iOS最强面试iOS开发攻城狮的集散地

objc_msgSend 底层实现

2018-07-16  本文已影响14人  天下林子

在平时开发中看似简单的方法调用,其实底层是做了多重操作的,只是我们不能具体看到,在此大致说一下objc_msgSend的执行流程,objc_msgSend的执行流程大致可以分为3个阶段
1.消息发送阶段
2.动态方法解析阶段
3.消息转发阶段

源码解读点我


消息发送阶段

retry:    
    runtimeLock.assertReading();

    // 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.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // 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.unlockRead();

    return imp;

通过上面的源码可以看到,通过isa找到自己的类对象的缓存Try this class's cache和方法列表Try this class's method lists中查找,如果找到则goto done,结束查找。

如果没有找到则会从父类的缓存和方法列表中查找Try superclass caches and method lists. 会有一个for循环for (Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass),拿到当前类的父类给当前类,for循环就是一层一层拿到父类,找到父类从父类中缓存中查找imp= cache_getImp(curClass, sel),如果有缓存则会将缓存填充到缓存中去log_and_fill_cache(cls, imp, sel, inst, curClass),此时是填充到消息类对象接受者的类对象中去,举个栗子:有一个类TestGoodStudent 继承自TestPerson类,TestGoodStudent *tt = [[TestGoodStudent alloc] init];当我对TestGoodStudent类创建的类对象tt进行调用父类的test方法时,则会从TestPerson类中找到test方法,并将test方法缓存到TestGoodStudent的类对象中去然后 goto done。

如果父类缓存中没有找到,则会去方法列表中查找,找到之后缓存到自己缓存中去,原理同缓存查找,找到之后 goto done

如果消息发送都没有找到,会向下走

    //没有找到,就会尝试消息解析resolver
    // No implementation found. Try method resolver once.

流程图如下:


image.png

动态方法解析阶段
动态方法解析:当在自己的缓存和所有父类的缓存中都没有找到调用的方法,自己的rw_t和父类的rw_t都没有找到方法,则就会进行动态方法解析。

另外是否进行动态方法解析还会根据triedResolver这个Bool值进行判断,没有解析过,就会进行动态解析,只要进行解析过一遍,则triedResolver就会标记为YES,就不会再进行解析,动态解析会根据类是类对象还是原类对象进行调用_class_resolveInstanceMethod和_class_resolveClassMethod,可以通过查看源码中objc-runtime-new.mm类中_class_lookupMethodAndLoadCache3方法

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
    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.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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.assertReading();

    // 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.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // 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.unlockRead();

    return imp;
}


如果没有实现resolveInstanceMethod,但是还会triedResolver=YES, 则goto rety
再次进行进入 if (resolver && !triedResolver), 条件不满足,则程序会顺序执行,会走_objc_msgForward_impcache进行消息转发

retry:    
    runtimeLock.assertReading();

    // 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.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // 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.unlockRead();

    return imp;

测试Demo中,动态方法实现了在resolveInstanceMethod中添加了other方法

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 获取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));

        // 动态添加test方法的实现
        //method_getImplementation 得到imp
        //method_getTypeEncoding  得到type
        class_addMethod(self, sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


动态方法解析流程如下:


image.png

消息转发阶段

当调用一个 NSObject 对象不存在的方法时,并不会马上抛出异常,而是会经过多层转发,层层调用对象的:-resolveInstanceMethod: --> -forwardingTargetForSelector: --> -methodSignatureForSelector: --> -forwardInvocation: 等方法,其中最后 -forwardInvocation: 是会有一个 NSInvocation 对象,这个 NSInvocation 对象保存了这个方法调用的所有信息,包括 Selector 名,参数和返回值类型,最重要的是有 所有参数值 ,可以从这个 NSInvocation 对象里拿到调用的所有参数值。

NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
anInvocation.target 方法调用者
 anInvocation.selector 方法名
  [anInvocation getArgument:NULL atIndex:0]

参考Demo,在MJPerson中没有实现test方法,则在main.m中调用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        [person test];
    }
    return 0;
}

调用test时,因为MJPerson没有实现test方法,则就会进行消息转发,调用forwardingTargetForSelector,如果forwardingTargetForSelector中

- (id)forwardingTargetForSelector1:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
      
        return [[MJCat alloc] init];
    }
    
    return [super forwardingTargetForSelector:aSelector];
    
}

这样会调用MJCat 的test方法, 如果没有实现test,则会调用methodSignatureForSelector

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        
        return nil;
    }
    
    return [super forwardingTargetForSelector:aSelector];
    
}

如果直接返回nil,则会调用methodSignatureForSelector , 并在该方法中返回一个方法签名,如果方法签名为空,则是不想处理该方法,会调用doesNotRecongnizeSector

整理后的源码如下

int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

            [receiver forwardInvocation:invocation];

            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue;
        }
    }

    if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }

    // The point of no return.
    kill(getpid(), 9);
}


消息转发流程如下:

image.png

objc_msgSend 编译报错

在使用objc_msgSend的时候发现会报如下错误,


image.png

究其原因是因为运行时代码里有一个宏定义


image.png

这个宏定义在objc-api.h里有它的原型


image.png

它其实是一条预编译指令,所以在Xcode的buildSettings里有了对应的选项,通过设置让这个宏定义不生效,上面的message.h里就能走到else里了,objc_msgSend 才能像c函数那样使用。

在这段注释中

These functions must be cast to an appropriate function pointer type 
  before being called. 

这句注释的异地就是这个函数在使用之前要转为合适的函数指针来使用,看起来有点怪怪的,但是存在是合理的。


说下怎么办?
使用运行时函数 objc_msgSend 的时候要把Xcode里这项配置关掉,才能使用C形式的函数调用:


image.png

这样就可以了~~


上一篇下一篇

猜你喜欢

热点阅读