探索objc_msgSend函数的实现流程
熟悉OC语言的Runtime(运行时)机制以及对象方法调用机制的开发者都知道,所有OC方法调用在编译时都会转化为对C函数objc_msgSend的调用。
objc_msgSend函数是所有OC方法调用的核心引擎,它负责查找真实的类或者对象方法的实现,并去执行这些方法函数。因调用频率是如此之高,所以要求其内部实现近可能达到最高的性能。这个函数的内部代码实现是用汇编语言来编写的。你可以在runtime源码下查看各种体系架构下的汇编语言的实现。
我们选择常用的arm64来查看:
可以看到,先通过LNilOrTargged判断对象是否是targetpoint类型或nil,然后对isa进行一系列的寄存器操作,最终得到LGetIsaDone(isa处理完毕),isa处理完毕后,开始执行CacheLookup NORMAL (在缓存cache_t里寻找imp,找到的话返回imp,未找到的话返回objc_msgSend_uncached),下面我们跳入CacheLookup的实现
可以看到有三种情况:CacheHit、CheckMiss、add。
先来查看CacheHit:
因为我们传过来的是NORMAL,所以紧接着执行TailCallCachedImp,回调imp。
再来看CheckMiss:
同样,传入的是NORMAL,所以会回调objc_msgSend_uncached,接着跳入objc_msgSend_uncached:
再跳入MethodTableLookup(可以猜到是去通过MethodTable方法列表去查找,事实也确实是这样的,结构体objc_class里有一个class_rw_t中存储着方法列表method_array_t (是一张哈希表,对method_t的name和imp进行一一对应),属性列表property_array_t,协议列表protocol_array_t 等内容),然后去回调FunctionPointer。再来查看MethodTableLookup:
也是咔咔一顿看不懂的汇编操作后到了class_lookupMethodAndLoadCache3,再跳入具体实现:
可以看到,该函数是通过c来实现的(终于告别了难缠的汇编):我们继续跳:
IMPlookUpImpOrForward(Classcls,SELsel,idinst,
boolinitialize,boolcache,boolresolver)
{
IMPimp =nil;
booltriedResolver =NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if(cache) {
// 如果cache是YES,则从缓存中查找IMP
imp =cache_getImp(cls, sel);
if(imp)returnimp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if(!cls->isRealized()) { 判断类是否已经被创建,如果没有被创建,则将类实例化
realizeClass(cls);
}
if(initialize && !cls->isInitialized()) { 第一次调用当前类的话,执行initialize的代码
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// 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
}
retry:
runtimeLock.assertLocked();
// Try this class's cache. 再次尝试获取这个类的缓存
imp =cache_getImp(cls, sel);
if(imp)gotodone;
// Try this class's method lists.
{ 如果没有从cache中查找到,则从方法列表中获取Method
Method meth = getMethodNoSuper_nolock(cls, sel);
if(meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
gotodone;
}
}
// Try superclass caches and method lists.
{ 如果还没有,就从父类缓存或者方法列表获取imp
unsignedattempts =unreasonableClassCount();
for(ClasscurClass = cls->superclass;
curClass !=nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if(--attempts ==0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp =cache_getImp(curClass, sel);
if(imp) {
if(imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
gotodone;
}
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.
Methodmeth =getMethodNoSuper_nolock(curClass, sel);
if(meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
gotodone;
}
}
}
// No implementation found. Try method resolver once.
if(resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst); 动态方法解析
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver =YES;
gotoretry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache; //消息转发
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
returnimp;
}
lookUpImpOrForward 这个方法里面篇幅很长里面介绍了以下几点(以下涉及了消息,动态方法解析,还有消息转发,我们这篇文章不做描述。) :
如果cache是YES,则从缓存中查找IMP。这里也就是说我们如果之前响应过的,在cache存过,就不需要下面的操作了
判断类是否已经被创建,如果没有被创建,则将类实例化
第一次调用当前类的话,执行initialize的代码
尝试获取这个类的缓存 (这里很多小伙伴就会质疑,为什么还要取一次内存,要知道OC是动态语言,在我们执行这个获取imp的时候,外界在开锁,解锁的时候是可以访问的,动态操作)
如果没有从cache中查找到,则从方法列表中获取Method
如果还没有,就递归从父类缓存或者方法列表获取imp
如果没有找到,则尝试动态方法解析:_class_resolveMethod
如果没有IMP被发现,并且动态方法解析也没有处理,则进入消息转发阶段:_objc_msgForward_impcache