Objective-C语言的一大特性就是动态的,根据官方文档的描述:在runtime以前,消息和方法并非绑定在一块儿的,编译器会把方法调用转换为objc_msgSend(receiver, selector)
,若是方法中带有参数则转换为objc_msgSend(receiver, selector, arg1, arg2, ...)
接下来咱们经过源码一窥究竟,在次以前咱们先了解几个基本概念html
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
复制代码
SEL其实就是一个不透明的类型它表明一个方法选择子,在编译期,会根据方法名字生成一个ID。数组
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
复制代码
他是一个函数指针,指向方法实现的首地址。缓存
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
复制代码
它保存了SEL到IMP和方法类型,因此咱们能够经过SEL调用对应的IMPbash
objc_msgSend的消息分发分为如下几个步骤: 咱们找到objc _msgSend源码,都是汇编,不过注释比较详尽app
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in r11
* Forwarding returned in Z flag
* r10 reserved for our use but not used
*
********************************************************************/
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
NilTest NORMAL
GetIsaFast NORMAL // r10 = self->isa
CacheLookup NORMAL, CALL // calls IMP on success
NilTestReturnZero NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r10
MESSENGER_END_SLOW
jmp __objc_msgSend_uncached
END_ENTRY _objc_msgSend
ENTRY _objc_msgLookup
NilTest NORMAL
GetIsaFast NORMAL // r10 = self->isa
CacheLookup NORMAL, LOOKUP // returns IMP on success
NilTestReturnIMP NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r10
jmp __objc_msgLookup_uncached
END_ENTRY _objc_msgLookup
ENTRY _objc_msgSend_fixup
int3
END_ENTRY _objc_msgSend_fixup
STATIC_ENTRY _objc_msgSend_fixedup
// Load _cmd from the message_ref
movq 8(%a2), %a2
jmp _objc_msgSend
END_ENTRY _objc_msgSend_fixedup
复制代码
就此咱们大概能够了解到其调用流程:less
判断receiver是否为nil,也就是objc_msgSend的第一个参数self,也就是要调用的那个方法所属对象ide
从缓存里寻找,找到了则分发,不然函数
利用objc-class.mm中_ class _lookupMethodAndLoadCache3方法去寻找selector优化
这里的**_ class _lookupMethodAndLoadCache3其实就是对lookUpImpOrForward**方法的调用:ui
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码
对第五个参数cache传值为NO,由于在此以前已经作了一个查找这里CacheLookup NORMAL, CALL,这里是对缓存查找的一个优化。
接下来看一下lookUpImpOrForward的一些关键实现细节
// Optimistic cache lookup
if (cache)
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}
复制代码
这里有个判断,是否须要缓存查找,若是cache为NO则进入下一步
// Check for freed class
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;
复制代码
_class _getFreedObjectClass的实现:
/***********************************************************************
* _class_getFreedObjectClass. Return a pointer to the dummy freed
* object class. Freed objects get their isa pointers replaced with
* a pointer to the freedObjectClass, so that we can catch usages of
* the freed object.
**********************************************************************/
static Class _class_getFreedObjectClass(void)
{
return (Class)freedObjectClass;
}
复制代码
注释写到,这里返回的被释放对象的指针,不是太理解,备注这之后再看看
// Check for +initialize
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172 } 复制代码
在方法调用过程当中,若是类没有被初始化的时候,会调用_class_initialize对类进行初始化,关于+initialize能够看以前的Runtime源码 +load 和 +initialize。
// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
// be added but ignored indefinitely because the cache was re-filled
// with the old value after the cache flush on behalf of the category.
retry:
methodListLock.lock();
// Try this class's cache. methodPC = _cache_getImp(cls, sel); if (methodPC) goto done; 复制代码
这里又作了一次缓存查找,由于上一步执行了+initialize
加锁这一部分只有一行简单的代码,其主要目的保证方法查找以及缓存填充(cache-fill)的原子性,保证在运行如下代码时不会有新方法添加致使缓存被冲洗(flush)。
// Try this class's method lists. meth = _class_getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, cls, meth, sel); methodPC = method_getImplementation(meth); goto done; } 复制代码
这里调用了log_ and_ fill_cache这个后面来看,接下里就是
// Try superclass caches and method lists.
curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. meth = _class_getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, curClass, meth, sel); methodPC = method_getImplementation(meth); goto done; } } 复制代码
关于消息在列表方法查找的过程,根据官方文档以下:
这里沿着集成体系对父类的方法列表进行查找,找到了就调用log_ and_ fill_cache
log_ and_ fill_cach的实现:
记录:
/***********************************************************************
* log_and_fill_cache
* Log this method call. If the logger permits it, fill the method cache.
* cls is the method whose cache should be filled.
* implementer is the class that owns the implementation in question.
**********************************************************************/
static void
log_and_fill_cache(Class cls, Class implementer, Method meth, SEL sel)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
_cache_fill (cls, meth, sel);
}
复制代码
内部调用了**_cache _fill**,填充缓存:
/***********************************************************************
* _cache_fill. Add the specified method to the specified class' cache. * Returns NO if the cache entry wasn't added: cache was busy,
* class is still being initialized, new entry is a duplicate.
*
* Called only from _class_lookupMethodAndLoadCache and
* class_respondsToMethod and _cache_addForwardEntry.
*
* Cache locks: cacheUpdateLock must not be held.
**********************************************************************/
bool _cache_fill(Class cls, Method smt, SEL sel)
{
uintptr_t newOccupied;
uintptr_t index;
cache_entry **buckets;
cache_entry *entry;
Cache cache;
cacheUpdateLock.assertUnlocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) {
return NO;
}
// Keep tally of cache additions
totalCacheFills += 1;
mutex_locker_t lock(cacheUpdateLock);
entry = (cache_entry *)smt;
cache = cls->cache;
// Make sure the entry wasn't added to the cache by some other thread // before we grabbed the cacheUpdateLock. // Don't use _cache_getMethod() because _cache_getMethod() doesn't // return forward:: entries. if (_cache_getImp(cls, sel)) { return NO; // entry is already cached, didn't add new one
}
// Use the cache as-is if it is less than 3/4 full
newOccupied = cache->occupied + 1;
if ((newOccupied * 4) <= (cache->mask + 1) * 3) {
// Cache is less than 3/4 full.
cache->occupied = (unsigned int)newOccupied;
} else {
// Cache is too full. Expand it.
cache = _cache_expand (cls);
// Account for the addition
cache->occupied += 1;
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
buckets = (cache_entry **)cache->buckets;
for (index = CACHE_HASH(sel, cache->mask);
buckets[index] != NULL;
index = (index+1) & cache->mask)
{
// empty
}
buckets[index] = entry;
return YES; // successfully added new cache entry
}
复制代码
这里还没找到实现则进入下一步,动态方法解析和消息转发,关于消息转发的细节咱们下篇再看。
在上面截出的源码中咱们屡次看到了cache,下面咱们就来看看这个,在runtime.h
和objc-runtime-new
cache的定义以下
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method _Nullable buckets[1] OBJC2_UNAVAILABLE;
};
复制代码
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
...
}
复制代码
这就是cache在runtime层面的表示,里面的字段和表明的含义相似
而在_ buckets中包含了一个个的cache_entry和bucket_t(objc2.0的变动):
typedef struct {
SEL name; // same layout as struct old_method
void *unused;
IMP imp; // same layout as struct old_method
} cache_entry;
复制代码
cache_entry定义也包含了三个字段,分别是:
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
...
}
复制代码
而bucket_t则没有了老的unused,包含了两个字段:
从runtime的源码咱们知道了方法调用的流程和方法缓存,有些附带的问题答案也就呼之欲出了:
_cache_fill (cls, meth, sel);
,因此即使是从父类取到的方法,也会存在类自己的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的metaclass里缓存一份。// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();
}
复制代码
当cache为空时建立;当新的被占用槽数小于等于其容量的3/4时,直接使用;不然调用cache->expand();
扩充容量:
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further // fixme this wastes one bit of mask newCapacity = oldCapacity; } reallocate(oldCapacity, newCapacity); } 复制代码
相关资料:
美团酒旅博文:深刻理解Objective-C:方法缓存
官方文档:Messaging