iOS探索 -- 消息的查找流程(二)

前面分析了方法查找的快速流程 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;
         }
     }
复制代码
  1. 首先使用快速查找查找当前类的缓存方法, 如果找到就直接结束
  2. 然后是调用 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;
             }
         }
     }
复制代码
  1. 首先是一个 for 循环结构, 从当前类的 superClass 父类 开始一级一级往上循环
  2. 在每次循环的过程中去查找方法, 首先查找类的缓存中有没有要找的 imp 实现, 如果没有找到就继续去查找类的 rw, 查找方法大致与类单独查找的相似
  3. 假如在某一个环节找到了方法的 imp, 就会首先调用 log_and_fill_cache 方法将方法的实现缓存到当前类中, 然后 goto done 结束流程
  4. 假如一直到最后都没有找到呢?
         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 重新查找了一遍上面的流程

  1. 最后的最后, 如果仍然没有找到, 会通过下面的代码将消息转发出去
         // No implementation found, and method resolver didn't help. 
     // Use forwarding.
 ​
     imp = (IMP)_objc_msgForward_impcache;
     cache_fill(cls, sel, imp, inst);
复制代码
  1. 如果找到了呢, 就会 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 的指向流程图:

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 确实存在这种特殊情况, 如果有更好的解释也欢迎一起讨论。

猜你喜欢

转载自juejin.im/post/7106916654453358622
今日推荐