前面分析了方法查找的快速流程 objc_msgSend
的汇编实现, 在 objc_msgSend
没有查找到相关方法的时候, 就会在最后调用一个方法 _class_lookupMethodAndLoadCache3
。接下来就来看一下这个流程是怎么实现的。
1. 方法慢速查找流程
//
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码
在方法内部又去调用了 lookUpImpOrForward
方法:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
// 快速查找, 从 _class_lookupMethodAndLoadCache3 调用时 cache = NO, 所以不会进入这里的快速查找, cache_getImp会进入到汇编实现
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search 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.
// 加锁
runtimeLock.lock();
// 安全检查
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
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) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
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.");
}
// 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);
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.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 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;
goto retry;
}
// 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();
return imp;
}
复制代码
方法很长代码行数很多, 接下来来分块研究一下到底都做了些什么:
准备工作:
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
复制代码
从方法的调用可以看到这里是不会进来的, 因为传进来的 cache = NO
。如果 cache
为 YES, 会调用 cache_getImp(cls, sel)
进行快速查找, 因为现在的流程是慢速查找, 所以这里是 NO。至于 cache_getImp
方法之所以说是快速查找, 是因为他也是用汇编实现的, 并且在实现中调用了 CacheLookUp
这个快速查找宏。
// 加锁
runtimeLock.lock();
//
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
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
}
复制代码
runtimeLock
是一种线程的读写锁, 加锁主要是为了安全的考虑checkIsKnownClass, isRealized 和 isInitiallized
都是为了判断类的合法性 以及 是否已经初始化realizeClass, _class_initialize
是对类以及父类和元类进行初始化相关操作, 之所以涉及到父类和元类, 这里有牵扯到了 isa 的指向图。因为方法查找时假如当前类没有找到, 会像 isa 的指向图一样一级一级的逐步往后查找- 所以准备工作就是保证 类, 父类以及元类都已经被加载, 然后在接下来就只需要进行逐步查找就可以了
方法查找:
当前类的查找
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
复制代码
- 首先使用快速查找查找当前类的缓存方法, 如果找到就直接结束
- 然后是调用
getMethodNoSuper_nolock
(注:这个方法的实现在后面的 '方法列表查询实现') 方法查号当前类的rw
下有没有要找的 imp, 如果有的话就先调用一次log_and_fill_cache
(该方法会调用cache_fill
方法进入方法缓存流程) 方法将查找到的结果缓存一下, 然后结束
在类的继承关系中查找
// Try superclass caches and method lists.
{
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.");
}
// 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);
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.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
复制代码
- 首先是一个 for 循环结构, 从当前类的
superClass 父类
开始一级一级往上循环 - 在每次循环的过程中去查找方法, 首先查找类的缓存中有没有要找的 imp 实现, 如果没有找到就继续去查找类的 rw, 查找方法大致与类单独查找的相似
- 假如在某一个环节找到了方法的 imp, 就会首先调用
log_and_fill_cache
方法将方法的实现缓存到当前类中, 然后goto done
结束流程 - 假如一直到最后都没有找到呢?
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;
goto retry;
}
复制代码
会进行一次容错处理, 这里的 if
必然会进入一次, 而在这里面调用了一个 _class_resolveMethod
方法, 从名字就能猜出来是为了修复方法的。在进行一次修复之后, 调用 goto retry
重新查找了一遍上面的流程
- 最后的最后, 如果仍然没有找到, 会通过下面的代码将消息转发出去
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
复制代码
- 如果找到了呢, 就会
return imp
将找到的实现传出去用来方法调用
方法列表的查找实现
接下来来看看方法列表的查找实现:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
//
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
return nil;
}
//
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
复制代码
2. 方法查找案例
首先是几个类的实现:
// Person 类
@interface Person : NSObject
- (void)sayHello;
+ (void)sayNB;
@end
@implementation Person
- (void)sayHello{
NSLog(@"%s",__func__);
}
+ (void)sayNB{
NSLog(@"%s",__func__);
}
@end
// Student 类
@interface Student : Person
- (void)sayCode;
+ (void)sayObjc;
@end
@implementation LGStudent
- (void)sayCode{
NSLog(@"%s",__func__);
}
+ (void)sayObjc{
NSLog(@"%s",__func__);
}
@end
// NSObject+Test NSObject 的分类
@interface NSObject (Test)
- (void)sayMaster;
- (void)sayNothing;
+ (void)sayEasy;
@end
@implementation NSObject (Test)
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)sayEasy{
NSLog(@"%s",__func__);
}
- (void)sayNothing{
NSLog(@"%s",__func__);
}
@end
复制代码
为了方便理解, 先放上 isa
的指向流程图:
1. 对象方法
// 声明对象
Student *s = [[Student alloc] init];
// 调用自己的方法
// 自己有-返回自己的
[s sayCode];
结果: -[Student sayCode]
// 调用父类的对象方法
// 自己没有-父类有-返回父类的
[s sayHello];
结果: -[Person sayHello];
// 调用 NSObject 的方法
// 自己没有-父类没有-NSObject有-返回 NSObject 的
[s sayMaster];
结果: -[NSObject(Test) sayMaster];
// 调用谁都没有的方法
// 自己没有-父类没有-NSObject也没有-崩溃
[s performSelector:@selector(saySometing)];
结果: unrecognized selector sent to instance 0x1012290e0
复制代码
根据结果然后再结合一下上面的指向图可以得出以下结论: 调用对象方法时的查找流程为 类对象 -> 父类对象 -> NSObject , 如果一直查找到 NSObject
都没有找到的话就会产生异常。
2. 类方法
// 调用自己的类方法
// 自己有-返回自己的
[Student sayObjc];
结果: -[Student sayObjc]
// 调用父类的类方法
// 自己没有-父类有-返回父类的
[Student sayNB]
结果: -[Person sayNB]
// 调用 NSObject 的
// 自己没有-父类没有-NSObject有-返回 NSObject 的
[Student sayEasy]
结果: -[NSObject(Test) sayEasy]
// 调用谁都没有的方法
// 自己没有-父类没有-NSObject也没有-崩溃
[Student performSelector:@selector(saySomeThing)];
结果: unrecognized selector sent to class 0x1000082d0
// 调用谁都没有的类方法, 但是在 NSObject 有名字相同的对象方法的实现
[Student performSelector:@selector(sayNothing)];
结果: -[NSObject(Test) sayNothing]
复制代码
类方法调用的查找流程跟对象方法的流程相似, 但是他们是两条不同的路线。类方法的查找流程为: 当前类的元类->根元类->NSObject , 类方法在查找到最上层的时候如果没有找到, 又回到了中间的 NSObject, 这就造成了上面测试中的最后一种情况。

调用类方法 sayNothing 的时候, 查找流程一直查找到 根元类( 根元类其实就是 NSObject ) 没有找到。
然后去转到 NSObject, 并且发现了相关对象方法的实现, 所以就去调用了查找到的对象方法。
关于方法的存储, 对象方法存储在类中, 而类方法存储在元类, 并且方法的存储里面并没有 '+' 和 '-' 的区别 (这里我的理解是 '+' 和 '-' 只是决定了方法的存储位置, 只是个人理解, 如果有异议欢迎讨论)。
ps: 这里描述的可能不是很好, 但是 NSObject 确实存在这种特殊情况, 如果有更好的解释也欢迎一起讨论。