iOS-底层原理-消息流程objc_msgSend分析之methodList(慢速查找)、动态方法决议、消息转发

1.什么是慢速查找、动态方法决议、消息转发

前面的博客介绍了,objs_msgSend查找cache的快速查找流程,即实例对象查找类对象cache类对象查找元类对象的cache,也就是说,通过isa指向找到第一个查找节点的cache,进而汇编语言进行快速查找,那么第一个节点的methodList怎么查找,什么时候查找,后面父类的cache和methodList呢?

父类的cache查找和我们前面分析的第一个节点的cache查找流程是否相同呢?这里isa和superclass看起来有些关联了,即通过isa确定第一个方法的查找节点,如果没有查找父类一直到NSObject到nil为止,其实还是没什么关系,各自独立的指针指向,只不过这种情况需要两者结合到一起使用,属于组队完成整个流程的过程。

cachemethodList都找不到的情况会有哪些补救措施呢?各级父类的cachemethodList查找以及找不到imp后的流程是这篇文章要分析的重点。

1.查找流程示意
对象方法查找流程
cache(类) ---> methodList(类) ---> cache(父类) ---> methodList(父类) ---> cache(NSObject ) ---> methodList(NSObject ) ---> 动态方法决议(添加一个imp以method形式返回) ---> 快速转发 --->慢速转发( 消息转发 )

类方法查找流程
cache(元类) ---> methodList(类) ---> cache(父元类) ---> methodList(父元类) ---> cache(根元NSObject ) ---> methodList(根元类NSObject ) ---> cache(根类NSObject ) ---> methodList(根类NSObject ) ---> 动态方法决议(添加一个imp以method形式返回) ---> 消息转发 ---> 快速转发 --->慢速转发

2.慢速查找、动态方法决议、消息转发
慢速查找其实就是起个名字,指的是逐级父类查找methodList的过程,快速查找就是汇编查找cache的过程
动态方法决议就是,没有对应的imp,利用runtime在给定的方法里面动态添加一个和sel对应的imp,用method结构返回,然后再来查找一次,即从第一个节点的methodList再查找一次,这里猜测应该是methodList和cache都被添加进去了

如果动态方法决议没有实现,走消息转发流程,快速转发就是用其他对象或者类来做这个事情

慢速转发就是通过方法签名获得一个任务,保留或者给其他类或者对象转发都可以,可以简单理解,这里我把这个sel通过签名的方法看出一个任务了,至于怎么做iOS系统就不用管了,知道有这么个事就行了,这样就不会执行崩溃流程了

cache和methodList这种自然的流程如果没有,看看能不能动态添加一个,如果也不添加,看看能不能转发给其他小伙伴处理,其他小伙伴如果也不处理,看看能不能把这个当成个事,至于什么时候做或者做不做都没关系,只有你口头承认这是个事,交给我了,iOS系统就不纠结了,即不执行后面的崩溃流程了,如果以上都不执行才会走到崩溃的流程里面来,

那么类在自然加载条件下cache和methodList是固定的,如果没有实现就是不存在imp,动态方法决议和消息转发的部分是开发给iOS开发者的,我们可以利用这两个节点,满足条件,就可以达到任而东南西北风,只有是sel查找imp,这种方法找不到 unrecognized selector的崩溃都可以避免掉了,不论对象方法还是类方法都能hook住了,好的一点是无论类继承链还是元类继承链最后都指向了NSObject根类,那我们可以在根类NSObject的分类 category里面搞事情了,达到统一入口,无入侵性,一次性解决unrecognized selector这种崩溃问题,还是很好的,其实动态方法决议也是苹果允许人为的向cache或者methodList的加入方法的机会

3.objc_msgSend汇编到c、c++层面的lookUpImpOrForward执行流程
_objc_msgSend如果在第一个节点的cache里面没有找到imp,则会执行如下汇编的代码段__objc_msgSend_uncached ---> MethodTableLookup ---> _lookUpImpOrForward,最后的_lookUpImpOrForward对应的是c语言里面的lookUpImpOrForward,

汇编c _lookUpImpOrForward ---> lookUpImpOrForward,去掉下划线
c汇编 lookUpImpOrForward ---> _lookUpImpOrForward,加上下划线

4. lookUpImpOrForward源码

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 找不到imp执行崩溃程序的指针
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        // The first message sent to a class is often +new or +alloc, or +self
        // which goes through objc_opt_* or various optimized entry points.
        //
        // However, the class isn't realized/initialized yet at this point,
        // and the optimized entry points fall down through objc_msgSend,
        // which ends up here.
        //
        // We really want to avoid caching these, as it can cause IMP caches
        // to be made with a single entry forever.
        //
        // Note that this check is racy as several threads might try to
        // message a given class for the first time at the same time,
        // in which case we might cache anyway.
        behavior |= LOOKUP_NOCACHE;
    }

    // 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();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    checkIsKnownClass(cls); // 判断类是否被认可
    // 是否实现和初始化,没有的话,递归实现和初始化所有的类,包括isa指向链的类和superclass继承链的类,用于下面的方法查找
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();

  // curClass临时变量,cls未改变
    curClass = cls;

    // The code used to lookup the class's cache again right after
    // we take the lock but for the vast majority of the cases
    // evidence shows this is a miss most of the time, hence a time loss.
    //
    // The only codepath calling into this without having performed some
    // kind of cache lookup is class_getInstanceMethod().

    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES 
//iOS系统&&64位真机,再查找一次第一个节点的cache,
// 但是这次与_objc_msgSend查找流程是不一样的,原理是一样的,否则就会死循环了
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock; // 找到imp,goto 跳出for循环
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            // 二分法查找methodList
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            // curClass当前类没有找到curClass赋值为父类,同时判断父类是不是nil,
            // 即根类NSObject已经查找过了,还是没有imp,则break结束for跳出for循环
           // 同时imp指向崩溃的指针地址
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) { //  NSObject ---> superclass == nil 跳出for循环
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        // 父类继承链中有循环则报错
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        // 查找父类的cache,cache_getImp()的实现也是汇编语言,但是与objc_msgSend汇编语言查询cache的流程是不一样的
        // 原理是一样的,有就返回imp,没有则返回nil继续后面的流程
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    // forward_imp 报错指针,报错之前会执行这里resolveMethod_locked 动态方法决议
    // behavior开关,动态方法决议只会来一次,每个方法执行一次,不是总共执行一次
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER; //  异或 不同为1,相同为0
        return resolveMethod_locked(inst, sel, cls, behavior);// resolveInstanceMethod如果没有动态添加imp会进来两次也说明了每个方法都有一次动态方法决议的机会包括resolveInstanceMethod
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        // 找到imp,写入superclass对应的类的cache里面去
        // b[i].set(b, sel, imp, cls());
        log_and_fill_cache(cls, imp, sel, inst, curClass); 
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

lookUpImpOrForward()函数核心逻辑
1.整个查找的流程逻辑上的分析
2.methodList二分查找逻辑上的分析
3.getImp()objc_msgSend查找cache上的区别
4.找到imp,写入到superclass对应的类的cache里面去
5.查找到NSObjectcachemethodList,依然没有找到imp,则此时imp = forward_imp,这是指向崩溃函数的指针,如果动态方法决议找到imp,则直接返回imp,如果找不到依然指向imp = forward_imp,但是此时是挂起的状态,允许iOS开发者实现后面的消息转发,如果转发有效,快速转发的话imp指向其他对象或者类方法的imp执行其代码,慢速转发的话sel包装成任务NSInvocation,即有认可iOS系统就不会崩溃了,也就是sel对应的imp有了着落,此时sel不再指向forward_imp,也就不会崩溃了。

5.getMethodNoSuper_nolock 源码

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods(); // cls->data() -->rw -->methods()是一个二维数组
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        //  getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

search_method_list_inline源码

ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->isExpectedSize();
    
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
            return m;
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name() == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}

findMethodInSortedMethodList源码

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    if (list->isSmallList()) {
        if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
        } else {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
        }
    } else {
        return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
    }
}

5.findMethodInSortedMethodList源码,二分查找主要在这里面,排序好的,地址由小到大

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin(); //指针数组开始的指针
    auto base = first; 
    decltype(first) probe; //动态指针

    uintptr_t keyValue = (uintptr_t)key; // 目标地址
    uint32_t count;
    
    // count >> 1 --> 二分
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1); 
        
        uintptr_t probeValue = (uintptr_t)getName(probe); // 动态指针地址
        
        if (keyValue == probeValue) { //  找到imp
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            //分类优先,while多个分类,两个相等说明分类和主类,分类和分类之间同名方法不会被覆盖
            // 分类在前,分类优先,所以,while循环查看前一个是否相等,即sel是否相同
            // 相同则优先分类的方法
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { 
                probe--; // 指针向前移动
            }
            return &*probe; // 返回method
        }
        
        if (keyValue > probeValue) { // 如果keyValue地址大于当前的probeValue
            base = probe + 1;  // base后移,对后面的进行二分
            count--; // count--
        }
    }
    
    return nil;
}

二分查找详细分析
假设总共8个method元素,sel对应的method在下标为0的位置上

value -- index 如下

0x10    0x20    0x30    0x40    0x50    0x60    0x70    0x80
0       1         2      3        4      5      6       7


first ---> 0
base ---> 0

for 循环第1次
count = 8, count != 0
probe ---> 4
两个if都不满足条件

for 循环第2次
count = 8, count != 0, (count >>= 1) = 4 ---> count = 4
probe = 0 + count >>= 1 
probe = 0 + 2
probe ---> 2
两个if都不满足条件

for 循环第4次
count = 4, count != 0, (count >>= 1) = 2 ---> count = 2
probe = 0 + count >>= 1 
probe = 0 + 1
probe ---> 1
两个if都不满足条件

for 循环第5次
count = 2, count != 0, (count >>= 1) = 1 ---> count = 1
probe = 0 + count >>= 1 
probe = 0 + 0
probe ---> 0
if (keyValue == probeValue) {} 条件满足返回method

可见keyValue在第一次二分的后面位置的时候probe逐一后指,count是不断二分的
probe ---> 4 ---> 2 ---> 1 ---> 0
count ---> 8 ---> 4 ---> 2 ---> 1

假设总共8个method元素,sel对应的method在下标为3的位置上

value -- index 如下

0x10    0x20    0x30    0x40    0x50    0x60    0x70    0x80
0       1         2      3        4      5      6       7


first ---> 0
base ---> 0

for 循环第1次
count = 8, count != 0
probe ---> 4
两个if都不满足条件

for 循环第2次
count = 8, count != 0, (count >>= 1) = 4 ---> count = 4
probe = 0 + count >>= 1 
probe = 0 + 2 = 2
probe ---> 2
if (keyValue > probeValue) {
    base = 2 + 1 = 3
    count = 3
}

for 循环第3次
count = 3, count != 0, (count >>= 1) = 1 ---> count = 1
probe = 3 + count >>= 1 
probe = 3 + 0
probe ---> 3
if (keyValue == probeValue) {} 条件满足返回method

可见keyValue在第一次二分的后面位置的时候probe逐一后指,count是不断二分的
probe ---> 4 ---> 2 ---> 3
count ---> 8 ---> 4 ---> 3 ---> 1

假设总共8个method元素,sel对应的method在下标为7的位置上

value -- index 如下

0x10    0x20    0x30    0x40    0x50    0x60    0x70    0x80
0       1         2      3        4      5      6       7


first ---> 0
base ---> 0

for 循环第1次
count = 8, count != 0
probe ---> 4
满足  if (keyValue > probeValue) {
    base ---> 5
    count = 7
}

for 循环第2次
count = 7, count != 0, (count >>= 1) = 3 ---> count = 3
probe = 5 + count >>= 1 
probe = 5 + 1 
probe ---> 6
满足  if (keyValue > probeValue) {
    base ---> 7
    count = 2
}

for 循环第3次
count = 2, count != 0, (count >>= 1) = 1 ---> count = 1
probe = 7 + count >>= 1 
probe = 7 + 0
probe ---> 7
if (keyValue == probeValue) {} 条件满足返回method

可见keyValue在第一次二分的后面位置的时候probe逐一后指,count是不断二分的
probe ---> 4 ---> 5 ---> 6 ---> 7
count ---> 8 ---> 3 ---> 1

6.cache_getImp()汇编源码_cache_getImp和_objc_msgSend的区别

cache_getImp,汇编查找类的缓存,这个方法与objc_msgSend第一阶段查找第一个节点cache,原理一样,但是流程不一样,_objc_msgSend ---> __objc_msgSend_uncached ---> MethodTableLookup ---> _lookUpImpOrForward
_cache_getImp ---> imp or nil

_cache_getImp汇编源码
CacheLookup GETIMP, 参数为GETIMP,找到返回imp,找不到返回nil

    STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0, 0
    CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

LGetImpMissDynamic: // 找不到返回nil
    mov p0, #0
    ret

LGetImpMissConstant: // 找不到返回nil
    mov p0, p2
    ret

    END_ENTRY _cache_getImp

_objc_msgSend 部分源码
CacheLookup NORMAL,这个参数是NORMAL,即找不到跳转到methodList查找流程里面来

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check // p0和空对比 p0寄存器第一个位置存储对象 判断对象是否为空
#if SUPPORT_TAGGED_POINTERS // 支撑小对象
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero // 为空return
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

7.对象方法和类方法整体查找流程
LGTeacher继承自LGPersonLGPerson继承自NSObject,下面的cache查找都是汇编语言实现的,目的是快速

对象方法查找流程,
teacher
---> cache(LGTeacher) ---> methodList(LGTeacher) ---> curClass = curClass->getSuperclass()&& != nil(curClass = LGPerson)
---> cache(LGPerson) ---> methodList(LGPerson) ---> curClass = curClass->getSuperclass()&& != nil(curClass = NSObject)
---> cache(NSObject) ---> methodList(NSObject) ---> curClass = curClass->getSuperclass() == nil
---> 跳出for循环 ---> 动态方法决议 ---> 快速消息转发 ---> 慢速消息转发

类方法查找流程
LGTeacher

---> cache(LGTeacher元类) ---> methodList(LGTeacher元类) ---> curClass = curClass->getSuperclass()&& != nil(LGPerson元类)
---> cache(LGPerson元类) ---> methodList(LGPerson元类) ---> curClass = curClass->getSuperclass()&& != nil(NSObject根元类)
---> cache(NSObject根元类) ---> methodList(NSObject根元类) ---> curClass = curClass->getSuperclass()&& != nil(NSObject根类)
---> cache(NSObject根类) ---> methodList(NSObject根类) ---> curClass = curClass->getSuperclass() == nil
---> 跳出for循环 ---> 动态方法决议 ---> 快速消息转发 ---> 慢速消息转发

8.log_and_fill_cache源码 sel和imp写入cache的流程

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}

insert 写入部分源码

void cache_t::insert(SEL sel, IMP imp, id receiver) {
    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m); //哈希算法函数利用地址唯一性大概率begin不会重复
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set(b, sel, imp, cls());  // cls() 写入当前cache对应的类或者元类
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));
}

cache获取class指针,前移16个字节

Class cache_t::cls() const
{
    // 当前的cache 向前移动16字节指向其class
    return (Class)((uintptr_t)this - offsetof(objc_class, cache));
}

9.resolveMethod_locked动态方法决议调用函数源码
这里面的核心思想就是在类的这两个方法+ (BOOL)resolveInstanceMethod:(SEL)sel+ (BOOL)resolveClassMethod:(SEL)sel里面,创建对应selimp,分别动态添加对象方法类方法methodcache,添加到或者元类里面,然后再次执行lookUpImpOrForwardTryCache这个函数,查找逻辑上与lookUpImpOrForward是一样的,只不过和lookUpImpOrForward是两个入口,目的是如果找到则返回imp,找不到执行后面的消息转发流程,就是给你指定的方法去添加method,然后,再查一次,有imp返回method,没有就返回nil,然后执行后面的消息转发流程

源码里英文注释1

// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);

源码里英文注释2

// Cache the result (good or bad) so the resolver doesn't fire next time. //
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

向resolveInstanceMethod发送消息核心代码 ---> 用于添加对象方法

SEL resolve_sel = @selector(resolveInstanceMethod:); 
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; 
bool resolved = msg(cls, resolve_sel, sel); //resolveInstanceMethod:

向resolveInstanceMethod发送消息核心代码 ---> 用于添加类方法

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

正常方法的objc_msgSend

LGPerson *person = [LGPerson alloc];   
objc_msgSend(person,sel_registerName("sayNB"));
[person sayNB];

很显然上面的源码里向resolveInstanceMethodresolveClassMethod发送消息的代码,与我们正常调用方法对应的objc_msgSend是不一样的,从调用上逻辑上源码注释上都有体现,逻辑上如果与普通的objc_msgSend一样的话就有可能存在循环而且也没意义,已经找过一次了,是动态添加一个,后面的代码去查询,然后返回imp,走后面的消息转发流程,调用上两种方式是不一样的,注释上见上面的英文源码注释

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 动态方法决议 :给一次机会 重新查询 在resolveInstanceMethod里添加sel对应的imp,即添加method到cache,对应的实现是添加imp,不是光写了这个方法
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls); // 给一次最后挽救的机会
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // 苹果源码英文注释,验证了上面的结论
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

resolveInstanceMethod源码

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:); //发送消息的sel

    // lookup resolve_sel ---> resolveInstanceMethod是否有这个方法,有这个方法调用才有意义,否则就崩溃了
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; //发送消息 //中间层拦截
    bool resolved = msg(cls, resolve_sel, sel); //resolveInstanceMethod:中间层使得imp有值,下次查找就有了imp
    // 在这个resolve_sel里面,给cls这个类添加一个sel为sel的method,用于返回当前sel缺少的method
    // 缓存这个返回的method,不管结果好坏,即有没有实现,但是resolveInstanceMethod:这个方法不负责启动再次查询sel对应的method是否已经实现并且缓存在类方法列表或者cache里面
    // 所以,需要下面的再次查询
    // Cache the result (good or bad) so the resolver doesn't fire next time. //
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

resolveClassMethod 源码

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }

2.代码验证动态方法决议和消息转发

消息转发分为快速转发和慢速转发,动态方法决议和消息转发可以在各自的类里面实现,但是实际工程里面各个类都这么实现的不太现实,基本上失去了意义,好的方式是在NSObject分类里面实现上面的流程

1.LGPerson代码

@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayPersonClass;
@end

2.调用

self.person = [[LGPerson alloc] init];
[self.person sayHello];
[LGPerson sayPersonClass];

3.NSObject 分类 NSObject+Resolve中的动态方法决议

+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(sayPersonClass)) { // 判断sel
        NSLog(@"resolveClassMethod == %@", NSStringFromSelector(sel));
        Class metaClass = objc_getMetaClass(class_getName([self class]));
        class_addMethod(metaClass, sel, (IMP)classFunction,"v@:");
        return YES;
    }
    return NO;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(sayHello)) { // 判断sel
        NSLog(@"resolveInstanceMethod == %@", NSStringFromSelector(sel));
        class_addMethod(self, sel, (IMP)instanceFunction, "v@:");
        return YES;
    }
    return NO;
}

int classFunction(id target, SEL cmd, ...) {
    return 0;
}

int instanceFunction(id target, SEL cmd, ...) {
    return 0;
}

Xcode 控制台输出

2022-06-05 09:36:36.696049+0800 方法流程001[2451:105070] resolveInstanceMethod == sayHello
2022-06-05 09:36:36.696129+0800 方法流程001[2451:105070] resolveClassMethod == sayPersonClass

4.动态方法决议小结
需要判断是哪个sel没有实现你让动态添加对应的sel,那么我都知道哪个没有实现的话根本不需要走到动态方法决议,直接在对应的类中添加对应的方法就可以了啊

如果动态方法决议没有实现,下一步会执行到消息转发,消息转发分快速消息转发和慢速消息转发

5.Teacher类代码

@interface Teacher : NSObject
- (void)sayHello;
+ (void)sayPersonClass;
@end

@implementation Teacher
- (void)sayHello {
    NSLog(@"sayHello in Teacher");
}

+ (void)sayPersonClass {
    NSLog(@"sayPersonClass in Teacher");
}
@end

6.NSObject 分类 NSObject+Resolve中的快速消息转发

//1.对象方法快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
    // 对应的类里面必须有aSelector对应的方法才可以,如果知道要崩溃
    if (aSelector == @selector(sayHello)) {
        NSLog(@"- forwardingTargetForSelector == %@", NSStringFromSelector(aSelector));

        return [[Teacher alloc] init];;
    }
    
    return nil;
}

//1.类方法快速转发,头文件里并没有这个方法
+ (id)forwardingTargetForSelector:(SEL)aSelector {
    // 对应的类里面必须有aSelector对应的方法才可以,如果知道要崩溃
    if (aSelector == @selector(sayPersonClass)) {
        NSLog(@"+ forwardingTargetForSelector == %@", NSStringFromSelector(aSelector));
        return [Teacher class];
    }
    
    return nil;
}

Xcode 控制台输出

2022-06-05 09:58:39.096094+0800 方法流程001[2686:121141] - forwardingTargetForSelector == sayHello
2022-06-05 09:58:39.096145+0800 方法流程001[2686:121141] sayHello in Teacher
2022-06-05 09:58:39.096184+0800 方法流程001[2686:121141] + forwardingTargetForSelector == sayPersonClass
2022-06-05 09:58:39.096213+0800 方法流程001[2686:121141] sayPersonClass in Teacher

7.快速消息转发小结
需要判断是哪个sel没有实现然后需要转发到其他类并且该类内部必须实现上面对应的方法,那么我都知道哪个没有实现的话根本不需要走到快速消息转发的流程,直接在对应的类中添加对应的方法就可以了,消息快速转发即转发给别的去处理,并且这个必须实现sel对应的方法

8.NSObject 分类 NSObject+Resolve中的慢速消息转发
methodSignatureForSelector和forwardInvocation组合使用

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"methodSignatureForSelector == %s", aSelector);
    NSMethodSignature * signature = [NSMethodSignature instanceMethodSignatureForSelector:@selector(showInstanceMessage:)];
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation anInvocation target == %@", [anInvocation target]);
    NSLog(@"forwardInvocation anInvocation selector == %@", NSStringFromSelector(anInvocation.selector));
}

- (void)showInstanceMessage:(NSString *)crashMessage {
    NSLog(@"showInstanceMessage == %@", crashMessage);
}

Xcode 控制台输出

2022-06-05 10:56:34.255052+0800 方法流程001[3140:158691] methodSignatureForSelector == sayHello
2022-06-05 10:56:34.255121+0800 方法流程001[3140:158691] forwardInvocation anInvocation target == 
2022-06-05 10:56:34.255157+0800 方法流程001[3140:158691] forwardInvocation anInvocation selector == sayHello

8.慢速消息转发小结
慢速消息只有转发对象方法的方法,目前没发现类方法的处理。慢速消息转发对象方法非常友好,只要实现了上面两个方法methodSignatureForSelector和forwardInvocation其他的不用做什么,就能避免对象方法找不到imp的这种崩溃

类方法在快速消息转发forwardingTargetForSelector,头文件里面没有对应的类方法,而且对象和类方法的处理都不是很友好,那么类方法怎么统一处理好呢,+ (BOOL)resolveClassMethod:(SEL)sel 动态方法决议里面有非常明确的针对类方法的处理函数,是否可以改进一下呢,因为如果知道哪个sel没有实现,直接写出来到对应的类里面就可以了,所以,无需判断sel才是统一的好的处理方式

9.NSObject 分类 NSObject+Resolve中的快速消息转发

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod == %@", NSStringFromSelector(sel));
    Class metaClass = objc_getMetaClass(class_getName([self class]));
    class_addMethod(metaClass, sel, (IMP)classFunction,"v@:");
    return NO;
}

Xcode 控制台输出

2022-06-05 11:15:50.602958+0800 方法流程001[3216:169916] resolveClassMethod == supportsSecureCoding
2022-06-05 11:15:50.613765+0800 方法流程001[3216:169724] resolveClassMethod == bundleForClass
2022-06-05 11:15:50.617928+0800 方法流程001[3216:169724] methodSignatureForSelector == sayHello
2022-06-05 11:15:50.617990+0800 方法流程001[3216:169724] forwardInvocation anInvocation target == 
2022-06-05 11:15:50.618028+0800 方法流程001[3216:169724] forwardInvocation anInvocation selector == sayHello
2022-06-05 11:15:50.618060+0800 方法流程001[3216:169724] resolveClassMethod == sayPersonClass

可见resolveClassMethod:类方法的处理,不进行sel判断会有一些系统的类方法进入,但是不会崩溃,与对象方法的慢速消息转发结合到一起可以没有针对性得处理对象方法和类方法找不到imp这样的崩溃,比较完美。

3.统一实现避免类方法和对象方法的崩溃 ---> unrecognized selector sent to class

NSObject+resolve 分类
对象方法没有实现避免崩溃:methodSignatureForSelector:和forwardInvocation:
类方法没有实现避免崩溃:resolveClassMethod:

你可能感兴趣的:(iOS-底层原理-消息流程objc_msgSend分析之methodList(慢速查找)、动态方法决议、消息转发)