iOS-Runtime消息机制

Runtime消息机制:

       本质上讲,OC的每一次方法调度都是一次消息的发送。其中方法调度的原理如下:

/*****************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd,...);
 *
 *****************************************************************/
    ENTRY objc_msgSend
    MESSENGER_START

    cbz r0, LNilReceiver_f    // 判断消息接收者是否为nil

    ldr r9, [r0]              // r9 = self->isa
    CacheLookup NORMAL           // 到缓存中查找方法

LCacheMiss:                      // 方法未缓存
    MESSENGER_END_SLOW
    ldr r9, [r0, #ISA]      
    b   __objc_msgSend_uncached

LNilReceiver:                    // 消息接收者为nil处理
    mov r1, #0
    mov r2, #0
    mov r3, #0
    FP_RETURN_ZERO
    MESSENGER_END_NIL
    bx  lr  

LMsgSendExit:
    END_ENTRY objc_msgSend

一个方法的调度主要包括以下几个步骤:

  • 判断接收者是否为nil,如果为nil,清空寄存器,消息发送返回nil
  • 到类缓存中查找方法,如果存在直接返回方法

没有找到缓存,到类的方法列表中依次寻找

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)
{
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();
    // 优先查找缓存
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    //runtimeLock在方法搜索期间保持,使方法查找+缓存填充原子相对于方法添加。否则添加一个类别,但是会无限期地忽略它,因为在代表类别的缓存刷新之后,缓存会用旧值重新填充
    runtimeLock.lock();
    checkIsKnownClass(cls);
    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    // 如果类未初始化,对其进行初始化。如果这个消息是initialize,那么直接进行类的初始化
    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.
    // 遍历缓存方法,如果找到,直接返回
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 从当前类的方法列表中查找
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // 沿着继承c链从父类的缓存查找,如果没查找到,从父类的方法列表中查找
    {
        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.");
            }
            
            // 父类缓存
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    //如果在父类方法中找到,,在当前类缓存列表中添加
                    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;
                }
            }
            
            // 父类方法列表查找.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // 没有找到任何的方法实现,进入消息转发第一阶段“动态方法解析”
    // 调用+ (BOOL)resolveInstanceMethod: (SEL)selector
    // 征询接收者所属的类是否能够动态的添加这个未实现的方法来解决问题
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        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;
    }
    // 仍然没有找到方法实现进入消息转发第二阶段
    //先后会调用 -(id)forwardingTargetForSelector: (SEL)selector
    // 以及 - (void)forwardInvocation: (NSInvocation*)invocation 进行最后的补救
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
 done:
    runtimeLock.unlock();
    return imp;
}
  • 以上为方法调用的全部过程,主要分为以下步骤:
  • 查找是否存在对应的方法缓存,如果存在直接返回调用。为了优化性能,方法的缓存使用了散列表的方式
  • 未找到缓存,到类本身或顺着类结构向上查找方法实现。如果在这个步骤中找到了方法的实现,那么将它加入到方法缓存中以便下次调用能快速找到。如果在类自身中没有找到方法实现,那么循环获取父类,重复上面的查找动作,找到后再将方法缓存到本类而非父类的缓存中
  • 未找到任何方法实现,触发消息转发机制进行最后补救
  • 其中消息转发分为两个阶段,第一个阶段我们可以通过动态添加方法之后让编译器再次执行查找方法实现的过程;第二个阶段称作备援的接收者,就是找到一个接盘侠来处理这个事件
  • 消息转发

//本例中run为实例发放,walk为类方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@",NSStringFromSelector(sel));

    if(sel == @selector(run)){
        class_addMethod([self class], sel, (IMP)class_getMethodImplementation([self class], @selector(testRun)), nil);
        return YES;
    }
    return [super resolveClassMethod:sel];
}

+ (BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"%@",NSStringFromSelector(sel));

    if(sel == @selector(walk)){
        bool success = class_addMethod(objc_getMetaClass([NSStringFromClass(self) UTF8String]), sel, (IMP)class_getMethodImplementation(objc_getMetaClass([NSStringFromClass(self) UTF8String]), @selector(testWalk)), "v@:");
//        //"v@:“,按顺序分别表示:
//       // v      : v表示返回值为void
//       // @    :参数id(self)
//       // :       :SEL(_cmd)对象
        return YES;
    }
    return  [super resolveClassMethod:sel];
}

//如果没有做上面两个添加方法操作,可以让其他对象来处理这个消息
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if([NSStringFromSelector(aSelector) isEqualToString:@"walk"]){
//        return [[Tools alloc]init]; //实例方法返回
        return [Tools class]; //类方法返回
    }
    return [super forwardingTargetForSelector:aSelector];
}


//methodSignatureForSelector需要和forwardInvocationf同时实现
//methodSignatureForSelector:和forwardInvocation:。methodSignatureForSelector:的作用在于为另一个类实现的消息创建一个有效的方法签名,必须实现,并且返回不为空的methodSignature,否则会crash。
//forwardInvocation:将选择器转发给一个真正实现了该消息的对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSString *selStr = NSStringFromSelector(aSelector);
    if ([selStr isEqualToString:@"run"]){
//        return [NSMethodSignature signatureWithObjCTypes:nil];
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//        return [NSMethodSignature methodSignatureForSelector:(IMP)class_getMethodImplementation([self class], @selector(testRun))];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //重新定义消息接收者   改变id
//    [anInvocation invokeWithTarget:[[Tools alloc] init]];
    //改变消息的sel
    anInvocation.selector = @selector(testRun);
    [anInvocation invokeWithTarget:self];
}

iOS-Runtime消息机制_第1张图片

缓存查找

bucket_t * cache_t::find(SEL s, id receiver)
{
    assert(s != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    //cache_hash   ( return (mask_t)(uintptr_t)sel & mask;  //获取存储在散列表中的hash下标)
    mask_t begin = cache_hash(s, m);
    mask_t i = begin;
    do {
        //如果找到则返回
        if (b[i].sel() == 0  ||  b[i].sel() == s) {
            return &b[i];
        }
        //hash存在冲突则继续向下查找
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)s, cls);
}

iOS-Runtime消息机制_第2张图片

当前类中的方法查找:

对于已排序好的列表,采用二分查找算法查找

没有排序的列表,采用遍历的方法查找

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;
        }
    }

#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;
}

文章部分内容参考:https://www.jianshu.com/p/f9cdaccc9f88


你可能感兴趣的:(iOS开发)