首先看下objc_msgSend的汇编入口。
UNWIND _objc_msgSend, NoFrame:定义了一些段存储数据对象,简单来说就是类似于结构体数据对象
NilTest:判断接受消息的对象是否为nil,如果为nil直接返回,这就是对nil发送消息无效的原因。
GetIsaFast:快速获取到isa指向的对象,是一个类对象还是元类对象。
CacheLookup:在类的缓存在查找selector对应的IMP。
如果没有再缓存中查到 就执行LCacheMiss下边的方法,跳转到__objc_msgSend_uncached
跳转到MethodTableLookup中,也就是要去方法列表中去查找IMP。
在MethodTableLookup中暴露出来的我们能看到的唯一的动作就是__class_lookupMethodAndLoadCache3这个方法。查找方法列表并加载缓存。
我们看到内部其实就是调用了lookUpImpOrForward方法,参数分别是:
是否需要进行初始化。
在+(void)initialize方法详解有介绍,用于后续和另外一个条件共同判断是否需要调用initialize方法。
是否需要在缓存中查找。
因为在msgSend的汇编语言中,已经调用过CacheLookup方法,没有再缓存中找到,才进入到这里,所以不再需要在缓存中查找。
是否需要动态解析。
消息转发的第一步。
接下来详细说下lookUpImpOrForward这个函数吧。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
//如果cache是yes,就从缓存中查找IMP,但是从Cache3函数进来,传入参数是NO,下边的if直接忽略
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
//判断类是否已经被创建,如果没有则将类实例化
if (!cls->isRealized()) {
//realizeClass方法的调用是类实例化,类呗创建的标志
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
retry:
runtimeLock.assertLocked();
//在该类下的缓存中查找IMP
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 没找到就在该类的方法列表中获取Method,进行对比,是要查找的方法就加入缓存,并获取IMP返回
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 获取当前类的父类,在其父类中查找
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = 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.");
}
// 在父类的缓存列表中查找
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 如果发现父类的方法符合条件,并且不再缓存中,在下边的函数中缓存方法.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// 如果imp在缓存中查找到的是一个_objc_msgForward_impcache标志,那说明之前在父类的缓存中
// 查找过这个方法,而且并没有找到对应的方法,这个标志是一个应该进入消息转发流程的标志。实际上
// 他只是结束了当前继承链查找的循环,下边的第一步就是进行动态解析,但是是否直接进行动态解析,
// 还是要通过条件判断
break;
}
}
// 父类的方法列表中查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 在继承链中的所有父类都没有找到索要的方法,通过传参的resolver来判断,是否需要动态解析,triedResolver用来限制动态解析只能进行一次
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
triedResolver = YES;
goto retry;
}
// 如果当前类和他所有父类都没有找到方法的实现,并且动态解析也没有找到方法的话,就将其标记为_objc_msgForward_impcache
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
最后 lookUpImpOrForward 方法也会把真正的IMP或者需要消息转发的 _objc_msgForward_impcache 返回,并最终传递到 objc_msgSend 中。而 _objc_msgForward_impcache 会再转化成 _objc_msgForward 或 _objc_msgForward_stret。
之后就是消息转发的流程了。这里所说的消息转发的流程就不包括【动态解析】这一步了。
具体查看消息转发机制的第二个阶段。
关于动态方法解析:
简述如何通过sel找到imp:
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称首先在当前类的缓存中查找,如果没有就去方法列表中查找,如果还是没有就去父类的缓存中查找, 如果查找结果是_objc_msgForward_impcache 就证明之前查找过,没有查找到,进入消息转发,如果能查找到对应的imp则直接调用,如果没有查找到任何结果,在去方法列表中查找,直到基类中还是没有查找到,那么在消息转发之前还有最后一次动态解析的机会,如果动态解析也没有查找到方法的实现, 则在当前类方法缓存中标记_objc_msgForward_impcache。代表查找不到