美文网首页iOS底层收集
iOS进阶-06 Objc_msgSend

iOS进阶-06 Objc_msgSend

作者: ricefun | 来源:发表于2020-02-12 16:06 被阅读0次

    Runtime

    Runtime 是一套为c/c++/汇编提供运行时功能的api。

    方法的本质探索

    先上代码:

    ###Person类定义
    @interface Person : NSObject
    - (void)test_cat;
    @end
    
    @implementation Person
    
    - (void)test_cat {
        NSLog(@"%s",__func__);
    }
    @end
    
    ###main函数调用
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [[Person alloc] init];
            [p test_cat];
        }
        return 0;
    }
    

    使用clang 命令,将main.m文件转化为C++文件mian.cpp

    //1.先cd到目标路径
    cd /Users/ricefun/Desktop/Runtime.demo
    //2.1使用clang命令查看(具体版)
    clang -rewrite-objc main.m -o mian.cpp
    

    阅读mian.cpp文件

    mian.cpp文件部分源码
    可以看到Person类的对象方法最终会转化为objc_msgSend()方法。
    结论:方法的本质:objc_msgSend发送消息,其中id为消息的接收者,sel为方法的编号

    objc_msgSend()

    在objc_msgSend()为了找到函数实现过程中总的经历了两部分,方法查找消息转发机制

    方法查找

    方法查找又分为快速查找(汇编部分)和慢速查找(C/C++部分)```

    快速查找(汇编部分)

    快速查找部分是用汇编写的,之所以这么做的原因有二:
    1.汇编部分其实是在cache中找实现,既然是cache目的就是提高速度,不就要求快吗,而汇编是一种机器语言,运算速度快。
    2.在C语言中不可能写一个函数来保留未知的参数并且跳转到任意一个韩函数。简单点:C语言不满足完成这件事的必要特性(能力)。

    ###汇编部分主要源码
    LReturnZero:
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
    
        END_ENTRY _objc_msgSend
    
    
        ENTRY _objc_msgLookup
        UNWIND _objc_msgLookup, NoFrame
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS
        b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
    #else
        b.eq    LLookup_Nil
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // p16 = class
    LLookup_GetIsaDone:
        CacheLookup LOOKUP      // returns imp
    
    #if SUPPORT_TAGGED_POINTERS
    LLookup_NilOrTagged:
        b.eq    LLookup_Nil // nil check
    
        // tagged
        mov x10, #0xf000000000000000
        cmp x0, x10
        b.hs    LLookup_ExtTag
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        b   LLookup_GetIsaDone
    
    LLookup_ExtTag: 
        adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
        ubfx    x11, x0, #52, #8
        ldr x16, [x10, x11, LSL #3]
        b   LLookup_GetIsaDone
    // SUPPORT_TAGGED_POINTERS
    #endif
    
    LLookup_Nil:
        adrp    x17, __objc_msgNil@PAGE
        add x17, x17, __objc_msgNil@PAGEOFF
        ret
    
        END_ENTRY _objc_msgLookup
    
        
        STATIC_ENTRY __objc_msgNil
    
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
        
        END_ENTRY __objc_msgNil
    
    
        ENTRY _objc_msgSendSuper
        UNWIND _objc_msgSendSuper, NoFrame
    
        ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
        CacheLookup NORMAL      // calls imp or objc_msgSend_uncached
    
        END_ENTRY _objc_msgSendSuper
    
        // no _objc_msgLookupSuper
    
        ENTRY _objc_msgSendSuper2
        UNWIND _objc_msgSendSuper2, NoFrame
    
        ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
        ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
        CacheLookup NORMAL
    
        END_ENTRY _objc_msgSendSuper2
    
        
        ENTRY _objc_msgLookupSuper2
        UNWIND _objc_msgLookupSuper2, NoFrame
    
        ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
        ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
        CacheLookup LOOKUP
    
        END_ENTRY _objc_msgLookupSuper2
    
    
    .macro MethodTableLookup
        
        // push frame
        SignLR
        stp fp, lr, [sp, #-16]!
        mov fp, sp
    
        // save parameter registers: x0..x8, q0..q7
        sub sp, sp, #(10*8 + 8*16)
        stp q0, q1, [sp, #(0*16)]
        stp q2, q3, [sp, #(2*16)]
        stp q4, q5, [sp, #(4*16)]
        stp q6, q7, [sp, #(6*16)]
        stp x0, x1, [sp, #(8*16+0*8)]
        stp x2, x3, [sp, #(8*16+2*8)]
        stp x4, x5, [sp, #(8*16+4*8)]
        stp x6, x7, [sp, #(8*16+6*8)]
        str x8,     [sp, #(8*16+8*8)]
    
        // receiver and selector already in x0 and x1
        mov x2, x16
        bl  __class_lookupMethodAndLoadCache3
    
        // IMP in x0
        mov x17, x0
        
        // restore registers and return
        ldp q0, q1, [sp, #(0*16)]
        ldp q2, q3, [sp, #(2*16)]
        ldp q4, q5, [sp, #(4*16)]
        ldp q6, q7, [sp, #(6*16)]
        ldp x0, x1, [sp, #(8*16+0*8)]
        ldp x2, x3, [sp, #(8*16+2*8)]
        ldp x4, x5, [sp, #(8*16+4*8)]
        ldp x6, x7, [sp, #(8*16+6*8)]
        ldr x8,     [sp, #(8*16+8*8)]
    
        mov sp, fp
        ldp fp, lr, [sp], #16
        AuthenticateLR
    
    .endmacro
    

    简单的捋一下汇编部分:

      1. ENTRY _objc_msgSend
      1. 对消息接受者(id self,sel _cmd) 判断处理
      1. 查找isa
      • 3.1 LNilOrTagged 找不到或者是TaggedPoint而类型
      • 3.2 LGetIsaDone 找到了isa
      1. GetClassFromIsa_p16 找到当前Class
      1. 在CacheLookup 缓存表中找
      • 5.1 CacheHit - 找到了那就调用 6(calls imp)
      • 5.2 CheckMiss - 找不到就 7(__objc_msgSend_uncached)
      • 5.3 add - 找到后,再添加到当前hash表中,为满足LRU原则
      1. calls imp
      1. objc_msgSend_uncached
      • 7.1 MethodTableLookup方法表查找
      • 7.2 __class_lookupMethodAndLoadCache3
      • 7.3 lookUpImpOrForward 这个方法之后就是C/C++语言部分
    慢速查找(C/C++部分)

    慢速查找(C/C++部分),将以源码的形式展示,主要查看几个函数,关键代码都有注释,不在赘述。
    lookUpImpOrForward()

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        //准备一些参数
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        //cache不一定是都是NO的,如果有cache == YES 会去查找一次
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
       //线程锁,防止读写冲突
        runtimeLock.lock();
        //验证下是否是有效类
        checkIsKnownClass(cls);
        //判断下类是否实现,没有就实现下
        if (!cls->isRealized()) {
            realizeClass(cls);//给bits->data.rw&ro ...赋值
        }
        // 初始化配置类的一些参数
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlock();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.lock();
        }
       /*****以上都是一些准备工作,下面才是真正的查找过程*/
         
     retry:    
        runtimeLock.assertLocked();
        //再次去cache找一次
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
       //遵循继承链(类->父类->NSObject->nil)查找
       //从 method lists 找
        {//这里括号是为了形成局部作用域,防止命名冲突
            Method meth = getMethodNoSuper_nolock(cls, sel);//看方法名字:不从父类中找,不就是从本身找吗
            if (meth) {//如果找到了,就进行cache缓存
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
       // 找不到,就冲父类的cache和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.unlock();
           //这个就是启动消息转发机制的方法,将在下面的消息转发机制中具体介绍这里不再过多赘述
            _class_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;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
        //内部实现[XXX xxx] unrecognized selector sent to instance逻辑
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
       //线程锁解锁
        runtimeLock.unlock();
       //返回imp
        return imp;
    }
    

    getMethodNoSuper_nolock() 函数

    static method_t *
    getMethodNoSuper_nolock(Class cls, SEL sel)
    {
        runtimeLock.assertLocked();
    
        assert(cls->isRealized());
        // fixme nil cls? 
        // fixme nil sel?
        //遍历方法list查找
        for (auto mlists = cls->data()->methods.beginLists(), 
                  end = cls->data()->methods.endLists(); 
             mlists != end;
             ++mlists)
        {
           //search_method_list()内部其实是用二分法查找
            method_t *m = search_method_list(*mlists, sel);
            if (m) return m;
        }
    
        return nil;
    }
    

    _objc_msgForward_impcache 汇编

        STATIC_ENTRY __objc_msgForward_impcache
    
        // No stret specialization.
        b   __objc_msgForward
    
        END_ENTRY __objc_msgForward_impcache
    
        
        ENTRY __objc_msgForward
           //调用__objc_forward_handler方法
        adrp    x17, __objc_forward_handler@PAGE
        ldr p17, [x17, __objc_forward_handler@PAGEOFF]
        TailCallFunctionPointer x17
        
        END_ENTRY __objc_msgForward
            ...
            ...
    

    _objc_forward_handler

    void *_objc_forward_handler = nil;
    void *_objc_forward_stret_handler = nil;
    
    #else
    
    // Default forward handler halts the process.
    __attribute__((noreturn)) void 
    objc_defaultForwardHandler(id self, SEL sel)
    {
        _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                    "(no message forward handler is installed)", 
                    class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                    object_getClassName(self), sel_getName(sel), self);
    }
    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    

    objc_defaultForwardHandler方法中就会展示我们日常熟悉的报错unrecognized selector sent to instance
    以上就是方法慢速查找流程

    消息转发机制

    消息转发机制一般分为4步(也可分为3步):

    • 1.动态方法决议(解析)
    • 2.快速转发阶段
    • 3.慢速转发阶段
    • 4.消息无法处理报错
    动态方法决议(解析)

    方法在报错之前其实还进行了一段消息转发流程先看源码:
    在上面的IMP lookUpImpOrForward()方法中有这样一个方法:

    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
        if (! cls->isMetaClass()) {//不是元类,调用Instance方法
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {//是元类,调用Class方法
            // 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);
            }
        }
    }
    

    class_resolveInstanceMethod实例方法为例,程序就会对当前类发出一个resolveInstanceMethod()方法,如果当前类重写过,就会进入处理,没有父类也有实现,那么最终会走到根父类NSObject中默认实现, 返回NO

    NSObject.mm
    + (BOOL)resolveClassMethod:(SEL)sel {
        return NO;
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return NO;
    }
    

    所以我们可以在重写的方法中添加方法,类似这样

    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(eat)) {
            IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
            Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
            const char *sleepType = method_getTypeEncoding(sleepMethod);
            return class_addMethod(self, sel, sleepImp, sleepType);
        }
        return [super resolveInstanceMethod:sel];
    }
    

    特别注意:上面是以实例方法为例的如果是类方法,在走了_class_resolveClassMethod类方法后,发现没有做处理,还会走_class_resolveInstanceMethod实例方法;即:
    类方法如果找不到 - 动态方法决议
    1.resolveClassMethod 处理了-->只注意元类2.resolveClassMethod 没有处理 --> resolveInstanceMethod - 类 - 元类 - NSObject
    所以:可以在NSObject分类中重写resolveInstanceMethod类方法,处理所有方法;eg代码如下

    #import "NSObject+MessageHandle.h"
    #import "runtime.h"
    @implementation NSObject (MessageHandle)
    //全局防崩溃处理
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        //if一个个判断太麻烦
        if (sel == @selector(say)) {
            IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
            Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
            const char *sleepType = method_getTypeEncoding(sleepMethod);
            return class_addMethod(self, sel, sleepImp, sleepType);
        }
    //    if (sel == @selector(sleep)) {
    //        ...
    //    }
        
        //综合处理  rf_路由_事物
        NSString *selString =  NSStringFromSelector(sel);
        if ([selString hasPrefix:@"rf_xxxxx"]) {//rf_是自定义方法开发
            //1.bug收集
            
            //2.异常处理
            if ([selString hasPrefix:@"rf_home_xxxxx"]) {
                RFRountTo(@"home");//跳转到首页
            } else if ([selString hasPrefix:@"rf_mine_xxxxx"]) {
                RFRountTo(@"mine");//跳转到我的
            }
            ...
        }
        
        //当然一般容错不会在方法决议里处理,在这里处理会压缩开发者的处理空间,一般放在完整消息转发中(NSInvocake)中,即最后一步
        
        return NO;
    }
    @end
    
    
    快速转发阶段

    当方法在动态决议中没有处理后,系统会调用forwardingTargetForSelector方法尝试着转发给其他类来处理

    instrumentObjcMessageSends(BOOL flag):调试程序堆栈流程开关;路径:/tmp/msgSends

    #import "Animal.h"
    #import "runtime.h"
    
    @implementation Person
    //自己没有;-- 那就交给其他对象,因为其他对象可能有
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(say)) {
            //返回其他对象
            return [Person alloc];//Person类有该方法
        }
        [super forwardingTargetForSelector:aSelector];
    }
    @end
    
    
    慢速转发阶段

    如果快速转发流程也没有处理,那么就会进入慢速转发流程-消息签名完整的消息转发

    //1.先方法签名:打包方法
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(say)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    //2.完整消息转发
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        //事情 - 事物 综合处理
        SEL sel = [anInvocation selector];
        if ([[Person alloc] respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:[Person alloc]];
        }else {
            [super forwardInvocation:anInvocation];
        }
    
        //当然你可以在这里做综合处理,类似于CTMediator
    }
    

    tips:在methodSignatureForSelectorforwardInvocation之间系统还会调用一次resolveInstanceMethod方法,进一步去匹配签名过程

    消息无法处理报错

    如果上述3步都没有处理,那么久会抛出异常[XXX xxx] unrecognized selector sent to instance

    完整代码
    #import "Person.h"
    #import "runtime.h"
    
    @implementation Person
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(say)) {
            IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
            Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
            const char *sleepType = method_getTypeEncoding(sleepMethod);
            return class_addMethod(self, sel, sleepImp, sleepType);
        }
        return [super resolveInstanceMethod:sel];
    }
    //自己没有;-- 那就交给其他对象,因为其他对象可能有
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(say)) {
            //返回其他对象
            return [Person alloc];//Person类有该方法
        }
        [super forwardingTargetForSelector:aSelector];
    }
    
    //1.先方法签名:打包方法
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(say)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    //2.完整消息转发
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        //事情 - 事物 综合处理
        SEL sel = [anInvocation selector];
        if ([[Person alloc] respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:[Person alloc]];
        }else {
            [super forwardInvocation:anInvocation];
        }
    }
    
    @end
    

    这个时候你再看这张图


    消息转发流程图
    面试题

    1.isKindOfClass&isMemberOfClass

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
        BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL re3 = [[Person class] isKindOfClass:[Person class]];
        BOOL re4 = [[Person class] isMemberOfClass:[Person class]];
        NSLog(@"\nre1:%hhd\nre2:%hhd\nre3:%hhd\nre4:%hhd",re1,re2,re3,re4);
        
        BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
        BOOL re6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
        BOOL re7 = [[Person alloc] isKindOfClass:[Person class]];
        BOOL re8 = [[Person alloc] isMemberOfClass:[Person class]];
        NSLog(@"\nre5:%hhd\nre6:%hhd\nre7:%hhd\nre8:%hhd",re5,re6,re7,re8);
        }
        return 0;
    }
    

    先思考五秒再看打印结果:

    2020-02-11 15:44:25.843470+0800 Test[44114:837672] 
    re1:1
    re2:0
    re3:0
    re4:0
    2020-02-11 15:44:25.844011+0800 Test[44114:837672] 
    re5:1
    re6:1
    re7:1
    re8:1
    

    解析:
    我们先看其方法定义

    + (BOOL)isMemberOfClass:(Class)cls {
        return object_getClass((id)self) == cls;
    }
    
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    ####
     object_getClass((id)self)
    这个方法中:如果self是对象,那返回类,如果self是类对象,那返回元类
    

    1-4是类方法,5-8是对象方法

    相关文章

      网友评论

        本文标题:iOS进阶-06 Objc_msgSend

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