美文网首页
iOS 消息发送查找&转发流程

iOS 消息发送查找&转发流程

作者: 85ca4232089b | 来源:发表于2020-03-19 15:31 被阅读0次

    objc源码下载地址
    通过断点结合C++源码调试流程
    汇编部分

    runtime-汇编.png

    OC中调用方法其实就是给类发送消息objc_msgSend -> _objc_msgSend_uncached ->MethodTableLookup->_class_lookupMethodAndLoadCache3(id, SEL, Class)

    objc_msgSend(void /* id self, SEL op, ... */ )
    
    objc_msgSend.png
    _objc_msgSend_uncached.png
    lookUpImpOrForward.png

    消息发送流程

    1. 首先检查这个sel是否需要忽略,如有垃圾回收装置就不会理会retain,release等等
    typedef struct {
        SEL name;     // same layout as struct old_method
        void *unused;
        IMP imp;  // same layout as struct old_method
    } cache_entry;
    struct method_t {
        SEL name;
        const char *types;
        MethodListIMP imp;
    
        struct SortBySELAddress :
            public std::binary_function<const method_t&,
                                        const method_t&, bool>
        {
            bool operator() (const method_t& lhs,
                             const method_t& rhs)
            { return lhs.name < rhs.name; }
        };
    };
    

    • 检查selector是否是垃圾回收方法,则将 IMP 结果设为 _objc_ignored_method。如果是则填充缓存_cache_fill(cls, (Method)entryp, sel);(这里entryp的类型是结构体cache_entry,将其强转为Method,并让methodPC指向该方法的实现即entryp->imp(_objc_ignored_method),然后跳转到done语句标号。否则进行下一步

    1. 检查这个selector是否为nil,OC允许对一个nil对象执行任何方法不会crash,因为运行时会忽略掉
    伪代码
    id objc_msgSend(id self, SEL _cmd, ...) {
      Class class = object_getClass(self);
      IMP imp = class_getMethodImplementation(class, _cmd);
      return imp ? imp(self, _cmd, ...) : 0;
    }
    IMP class_getMethodImplementation(Class cls, SEL sel)
    {
        IMP imp;
    
        if (!cls  ||  !sel) return nil;
    
        imp = lookUpImpOrNil(cls, sel, nil, 
                             YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
    
        // Translate forwarding function to C-callable external version
        if (!imp) {
            return _objc_msgForward;
        }
    
        return imp;
    }
    IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
    {
        IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
        if (imp == _objc_msgForward_impcache) return nil;
        else return imp;
    }
    

    • lookUpImpOrNil方法中会调用lookUpImpOrForward函数查找一边缓存 此时传入的cache默认是YES.所以在后面_class_lookupMethodAndLoadCache3中默认的cache时NO,避免两次搜索缓存。
    • _objc_msgForward_impcache 汇编程序入口作为缓存中消息转发的标记
    • 如果imp==_objc_msgForward_impcache 直接return nil.
    • 负责直接返回
    • 这个方法不会进行消息的转发,而直接返回nil,这个倒是比较有趣,明明调用lookUpImpOrForward可以直接进行消息转发,可是这里偏不这样做,调用消息转发返回nil的函数,然后判断imp为nil时,自己手动返回_objc_msgForward,进行消息转发。
    ⚠️ class_getMethodImplementation(),method_getImplementation()返回值会不一样?

    IMP method_getImplementation(Method m)
    {
        return m ? m->imp : nil;
    }
    

    如果这个method不存在,直接返回nil,而
    class_getMethodImplementation()会经历消息转发机制,最后返回的是forwardInvocation的结果,而这部分是不开源的,也不知道具体是怎么返回的,但每次运行确实是会返回的一个固定的地址,这个地址可能和NSInvocation这个对象的内存地址有关.

    1. 查找这个类的实现的IMP,先存缓存的方法列表cache中查找,执行过的方法会缓存在该列表中,如果找到了就会运行对性的函数执行相应的代码
    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
    {        
        return lookUpImpOrForward(cls, sel, obj, 
                                  YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        Class curClass;
        IMP methodPC = nil;
        Method meth;
        bool triedResolver = NO;
    
        methodListLock.assertUnlocked();
    
        // Optimistic cache lookup
        if (cache) {
            methodPC = _cache_getImp(cls, sel);
            if (methodPC) return methodPC;    
        }
    if (cls == _class_getFreedObjectClass())
            return (IMP) _freedHandler;
    }
        if (initialize  &&  !cls->isInitialized()) {
            _class_initialize (_class_getNonMetaClass(cls, inst));
      •  initialize默认是no,关联一下initialize的调用,其实是走的消息转发流程
    //类对象都还没有初始化 ,并不影响initialize 。实力对象是不依赖 initialize
        }
    //
     retry:
        methodListLock.lock();
    
        methodPC = _cache_getImp(cls, sel);
        if (methodPC) goto done;
        meth = _class_getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, cls, meth, sel);
            methodPC = method_getImplementation(meth);
            goto done;
        }
        curClass = cls;
        while ((curClass = curClass->superclass)) {
            // Superclass cache.
            meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
            if (meth) {
                if (meth != (Method)1) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, curClass, meth, sel);
                    methodPC = method_getImplementation(meth);
                    goto done;
                }
                else {
                    break;
                }
            }
    
            // Superclass method list.
            meth = _class_getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, curClass, meth, sel);
                methodPC = method_getImplementation(meth);
                goto done;
            }
        }
    
        // No implementation found. Try method resolver once.
    
        if (resolver  &&  !triedResolver) {
            methodListLock.unlock();
            _class_resolveMethod(cls, sel, inst);
            triedResolver = YES;
            goto retry;
        }
        _cache_addForwardEntry(cls, sel);
        methodPC = _objc_msgForward_impcache;
     done:
        methodListLock.unlock();
    
        return methodPC;
    

    • objc_msgSend最开始就在缓存中进行了搜索,所以有了一个很有趣的方法_class_lookupMethodAndLoadCache3,这个方法在调用lookUpImpOrForward时传入cache是NO,避免两次搜索缓存),因为在上面lookUpImpOrNil方法中已经查找过一遍缓存。
    • methodPC = _cache_getImp(cls, sel);根据cls和sel在本类缓存中查找有没有这个方法,如果有直接返回,
    • 释放检测: _class_getFreedObjectClass:检测发送消息的对象是否已经被释放,如果已经释放,则返回_freedHandler 的IMP

      // Check for freed class
        if (cls == _class_getFreedObjectClass())
            return (IMP) _freedHandler;
    
    1. 如果没有找到,就在该类的缓存中和方法列表methodlist中查找是否有相应的方法,找到则执行
     retry:
        methodListLock.lock();
        // Try this class's cache.
        methodPC = _cache_getImp(cls, sel);
        if (methodPC) goto done;
        // Try this class's method lists.
        meth = _class_getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, cls, meth, sel);
            methodPC = method_getImplementation(meth);
            goto done;
        }
    

    • methodListLock.lock():考虑运行时方法的动态添加,加锁是为了使方法搜索和缓存填充成为原子操作。否则category添加时刷新的缓存可能会因为旧数据的重新填充而被完全忽略掉。
    • 上一步缓存中没有发现,进入类的class's cache缓存中查找,找到了就执行goto done,否则下一步
    • 类的缓存中没有找到,然后进入本类的方法列表中查找,如果找到了就进行 goto done,否则下一步
    • 如果以上都没找到的话 ,下一步

    1. 如果没有找到,则沿着继承树在父类中查找,一直找到NSObject为止
        curClass = cls;
        while ((curClass = curClass->superclass)) {
            // Superclass cache.
            meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
            if (meth) {
                if (meth != (Method)1) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, curClass, meth, sel);
                    methodPC = method_getImplementation(meth);
                    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.
            meth = _class_getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, curClass, meth, sel);
                methodPC = method_getImplementation(meth);
                goto done;
            }
        }
    

    • while循环在父类中寻找 父类中也是同样的,先在父类的缓存Superclass cache,没有再找父类的方法列表Superclass method list.
    • 如果找到了就执行 log_and_fill_cache加入缓存
    父类查找流程图:


    supperClass.png
    1. 如果还是没有找到,则执行消息转发流程

    消息转发流程:

    No implementation found. Try method resolver once.只会执行一次

    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
        if (! cls->isMetaClass()) {//不是元类
            // try [cls resolveInstanceMethod:sel]
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {//是元类
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    
    // 第一阶段 动态方法解析
    + (BOOL)resolveInstanceMethod:(SEL)selector//对象方法
    + (BOOL)resolveClassMethod:(SEL)selector //处理的是类方法
    {
        if (sel == @selector(run:)) {
            SEL readSEL = @selector(readBook);
            Method readM= class_getInstanceMethod(self, readSEL);
            IMP readImp = method_getImplementation(readM);
            const char *type = method_getTypeEncoding(readM);
            return class_addMethod(self, sel, readImp, type);
          //    class_addMethod([self class], sel, (IMP)testRun, "v@:");
              return YES;
          }
        return  [super resolveInstanceMethod:sel];
    }
    // 第二阶段:第三者的处理
    - (id)forwardingTargetForSelector:(SEL)selector{
        if (aSelector == @selector(run:)) {
            thridTest * p = [thridTest new];
            return p;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    // 第三阶段: 标准消息转发流程
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        if([NSStringFromSelector(aSelector) isEqualToString:@"run:"]){
            return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        }
        return [super methodSignatureForSelector:aSelector];
        
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        SEL  sel = [anInvocation selector];
       thridTest *p = [[thridTest alloc] init];
        if ([p respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:[[thridTest alloc] init]];
            return ;
        }
        return [super forwardInvocation:anInvocation];
    }
    // 第四阶段:报错
    - (void)doesNotRecognizeSelector:(SEL)aSelector{
        NSLog(@"不能识别的方法: %@",NSStringFromSelector(aSelector));
    }
    void  testRun(id self ,SEL _cmd ,NSString* str){
        NSLog(@"1234567890");
    }
    

    消息查找流程图:


    消息查找流程.png

    最后补充一下cache_t的知识

    struct cache_t {
        struct bucket_t *_buckets;
        mask_t _mask;
        mask_t _occupied;
    //删除了一些内容
    }
    

    • 首先是一个结构体
    • _buckets:数组,是bucket_t结构体的数组,bucket_t是用来存放方法的SEL内存地址和IMP的。
    • _mask的大小是数组大小 - 1,用作掩码。(因为这里维护的数组大小都是2的整数次幂,所以_mask的二进制位000011, 000111, 001111)刚好可以用作hash取余数的掩码。刚好保证相与后不超过缓存大小。
    • _occupied是当前已缓存的方法数。即数组中已使用了多少位置。

    缓存策略:
    _mask->capacity()->expand()

    static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
    {
        cacheUpdateLock.assertLocked();
    
        // Never cache before +initialize is done
        if (!cls->isInitialized()) return;
    
        // Make sure the entry wasn't added to the cache by some other thread 
        // before we grabbed the cacheUpdateLock.
        if (cache_getImp(cls, sel)) return;
    
        cache_t *cache = getCache(cls);
        cache_key_t key = getKey(sel);//这里将sel转化为cache_key_t,也就是数字,主要是为了方便查找、。
    
        // Use the cache as-is if it is less than 3/4 full
        mask_t newOccupied = cache->occupied() + 1;
        mask_t capacity = cache->capacity();
        if (cache->isConstantEmptyCache()) {//如果缓存为空
            // Cache is read-only. Replace it.
            //执行清理操作
            cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
        }
        else if (newOccupied <= capacity / 4 * 3) {
            // Cache is less than 3/4 full. Use it as-is.
            //当容量超过capacity的四分之三时,执行扩容的逻辑。
        }
        else {
            // Cache is too full. Expand it.
            //扩容为原来的两倍
            cache->expand();
        }
    
        // Scan for the first unused slot and insert there.
        // There is guaranteed to be an empty slot because the 
        // minimum size is 4 and we resized at 3/4 full.
        bucket_t *bucket = cache->find(key, receiver);
        if (bucket->key() == 0) cache->incrementOccupied();//如果没有找到缓存,那么就将这个方法缓存起来。
        bucket->set(key, imp);
    }
    

    扩容清理函数

    void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
    {
        bool freeOld = canBeFreed();
    
        bucket_t *oldBuckets = buckets();
        bucket_t *newBuckets = allocateBuckets(newCapacity);
    
        // Cache's old contents are not propagated. 
        // This is thought to save cache memory at the cost of extra cache fills.
        // fixme re-measure this
    
        assert(newCapacity > 0);
        assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
    
        setBucketsAndMask(newBuckets, newCapacity - 1);
        
        if (freeOld) {
            cache_collect_free(oldBuckets, oldCapacity);
            cache_collect(false);
        }
    }
    

    • 首先获取旧的buckets。
    • 再根据容量初始化一个新的buckets。
    • 再讲新的buckets和mask设置上去,并将_occupied清0。
    • 最后将旧的buckets释放掉。

    cache在执行扩容的同时会清理掉旧的buckets,也就是说,之前缓存的方法会被清空,这是LRU淘汰算法的一个应用。
    

    缓存流程

     * Cache readers (PC-checked by collecting_in_critical())
     * objc_msgSend*
     * cache_getImp
     * 
     * Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked)
     * cache_fill         (acquires lock)
     * cache_expand       (only called from cache_fill)
     * cache_create       (only called from cache_expand)
     * bcopy               (only called from instrumented cache_expand)
     * flush_caches        (acquires lock)
     * cache_flush        (only called from cache_fill and flush_caches)
     * cache_collect_free (only called from cache_expand and cache_flush)
    

    详细的缓存策略

    相关文章

      网友评论

          本文标题:iOS 消息发送查找&转发流程

          本文链接:https://www.haomeiwen.com/subject/azdlyhtx.html