阅读 35

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

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
文章标签