美文网首页
runtime消息传递与转发

runtime消息传递与转发

作者: BLUEVIPIOS_ | 来源:发表于2021-08-09 21:48 被阅读0次

    官方文档及资源地址
    Documentation Archive
    Apple Open Source
    查看runtime开源文件 arm64 位
    objc-msg-arm64.s - ARM64 code to support objc messaging
    将Object-C 语言转换为C++:

    xcrun -sdk iphonesimulator clang -rewrite-objc main.m
    

    以上指令 会生成.cpp文件 查看cpp文件代码 与官方源码 得知
    objc_msgSend 是用汇编写的。
    C语言不能通过写一个函数,去跳转到任意的指针,汇编可以利用寄存器实现,C语言使用“静态绑定”,也就是说,在编译时就能决定运行时所应调用的函数,如果待调用的函数地址无法硬编码在指令之中,那就要在运行期读取出来,使用“动态绑定”,我们都知道c语言是面向过程,由编译器进行处理,显然无法实现这样的需求。而runtime是运行时,在运行的时候会进行特殊操作访问不同的内存空间,因此oc具备动态特性。

    1.对象及方法本质
    @autoreleasepool {
            YJPerson * P = [YJPerson new];
            [P run];    
    }
    //编译后 (环境依赖部分代码暂不考虑 此处没有粘贴出来)
    
    #pragma clang assume_nonnull begin
    #ifndef _REWRITER_typedef_YJPerson
    #define _REWRITER_typedef_YJPerson
    typedef struct objc_object YJPerson;
    typedef struct {} _objc_exc_YJPerson;
    #endif
    
    struct YJPerson_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
    };
    
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            YJPerson * P = ((YJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YJPerson"), sel_registerName("new"));
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)P, sel_registerName("run"));
    }
    void runImp (id self ,SEL _cmd){
    }
    

    将.m文件转换为C++文件 即可得出

    • 对象的本质:结构体, 占用内存大小
    • 方法的本质就是 _objc_msgSend 发消息
      接下来 看下 消息传递与转发。

    2.消息传递

    消息发送 _objc_msgSend

    void objc_msgSend(id self, SEL cmd, ...)
    //接受两个或两个以上的参数,第一个参数代表接收者,第二个参数代表SEL(SEL是选择子的类型),后续参数就是消息中的那些参数.编译器会进行转换
    id returnValue =  objc_msgSend(someObject,@selector(messageName:), parameter);
    

    以下几个概念需要搞清

    1. objc_class
      重要成员(也都是结构体 建议看下源码 很有意思的)
    • objc_method_list($)
      方法列表
    • objc_cache($)
      缓存列表 method_name:method_imp 。 key:value的形式
    • 结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等
    
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    } OBJC2_UNAVAILABLE;
    
    struct objc_method_list {
        struct objc_method_list *obsolete;
        int method_count;
    #ifdef __LP64__
        int space;
    #endif
        /* variable length structure */
        struct objc_method method_list[1];
    };
    
    struct objc_method {
        SEL method_name;
        char *method_types;    /* a string representing argument/return types */
        IMP method_imp;
    };
    
    
    1. 源码部分分析
    调用 objc_msgSend 后 系统会进行一系列的复杂操作
    - 首先,通过 obj 的 isa 指针找到它的 class ;
    - 在 class 的 method list 找 对应的 func ;
    - 如果 class 中没到 func,继续往它的 superclass 中找 ;
    - 一旦找到 func 这个函数,就去执行它的实现IMP .
    由于每个消息都需要遍历一次,效率会比较低。
    objc_class 中另一个重要成员 objc_cache把经常被调用的函数缓存下来,大大提高函数查询的效率。把 func 的 method_name 作为 key ,method_imp 作为 value 给存起来。
    当再次收到 func 消息的时候,直接在 cache 里找到,避免去遍历 objc_method_list
    

    接下来看下详细的具体流程

    • ENTRY _objc_msgSend 入口
      判断接收者recevier是否为空,为空则返回,不为空,就处理isa。
      Objective-C 是一门面向对象的语言,对象又分为实例对象、类对象、元类对象以及根元类对象。它们是通过一个叫 isa 的指针来关联起来,具体关系如下图:
      isa superclass
      案例点击可查看
    // _objc_msgSend 入口
        ENTRY _objc_msgSend
    // 窗口
        UNWIND _objc_msgSend, NoFrame
    // tagged pointer 特殊 数据类型 数据非常小
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
        b.eq    LReturnZero
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // p16 = class
    LGetIsaDone:
        CacheLookup NORMAL      // calls imp or objc_msgSend_uncached
    

    建议仔细的看下这个完整的文件 相信自己能看懂 就行。官方代码逻辑非常的清晰。

    首先进行缓存检查和类型判断
    LNilOrTagged

    taggedPoint:存储小值类型,地址中包含值和类型数据,能进行快速访问数据,提高性能

    LGetIsaDone

    通过汇编指令b LGetIsaDone跳转到CacheLookup,对缓存进行快速的查找,如果有缓存就直接返回,由于这一步是通过汇编执行,所以是快速查找,效率很高(这里存在查找的过程)


    image.png
    CacheLookup 分为三种
    • CacheHit
      找到了,则调用CacheHit进行call or return imp
    • CheckMiss
      找不到 __objc_msgSend_uncached
    • add
      别的地方找到了这imp就进行add操作,方便下一次快速的查找。
    MethodTableLookup

    如果来到这里 说明在缓存里面不存在
    先找自己,如果自己没有IMP,然后找父类的缓存,如果没有,循环递归查找父类的IMP,一直找到NSObject,如果还是没有,接下来就开始动态方法解析,如果动态方法解析没有实现,接下来再调用消息转发,流程如下,核心方法----- lookUpImpOrForward


    image.png

    底层源码如下

    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 缓存中有IMP,直接返回
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        //runtimeLock 加锁 保证线程安全(数据安全) 保证 旧数据不再重新填充
        // 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.lock();
        checkIsKnownClass(cls);
    
        if (!cls->isRealized()) {
            realizeClass(cls);
        }
    
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlock();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.lock();
            // 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.assertLocked();
    
        // Try this class's cache. 缓存中有IMP,直接返回
    
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        // Try this class's method lists. 1 找自己的IMP,找到加入方法缓存
        {
            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. 2 找父类 这一步大致与上一步相同 只是找的是 父类 上一步是自己
        {
            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.
       // 没有IMP,调用一次resolver动态方法解析,通过triedResolver变量来控制该方法只走一次
    
        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.消息转发
    
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlock();
    
        return imp;
    }
    

    消息传递:底层的确很复杂,涉及到寄存器位运算,下面是流程图
    流程图
    func没有找到,通常情况下,程序会在运行时挂掉并抛出 unrecognized selector sent to … 的异常。但在异常抛出前,Objective-C 的运行时会有三次拯救程序的机会。继续看消息转发的过程

    3.消息转发

    对象在收到消息无法处理,将调用resolver解析

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

    1.+ (BOOL)resolveInstanceMethod:(SEL)selector;
    对象在收到无法解读的消息后调用此函数,参数就是那个未知的SEL(字符编码),其返回值为Boolean类型,表示这个类是否能新增一个处理此SEL的方法。让你有机会提供一个函数实现。
    如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。此方法常用来实现@dynamic属性、访问CoreData框架中NSManagedObjects对象

    如果 resolve 方法返回 NO ,运行时就会移到下一步:消息转发

    +(BOOL)resolveInstanceMethod:(SEL)sel{
        //方法名
        NSString *selStr = NSStringFromSelector(sel);
        if ([selStr isEqualToString:@"name"]) {
            //增加name方法的实现
            class_addMethod(self, sel, (IMP)nameGetter, "@@:");
            return YES;
        }
        if ([selStr isEqualToString:@"setName:"]) {
            class_addMethod(self, sel, (IMP)nameSetter, "v@:@");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    // 或者 runtime api 
    IMP fooIMP = imp_implementationWithBlock(^(id _self) {
        NSLog(@"Doing foo");
    }); 
    class_addMethod([self class], aSEL, fooIMP, "v@:");
    

    2.- (id)forwardingTargetForSelector:(SEL)aSelector
    接收者有第二次机会处理未知的SEL,就是把这条消息转给其他接收者来处理,这一步无法操作转发的消息。如要修改或者处理的话就需要触发完整的消息转发机制

    -(id)forwardingTargetForSelector:(SEL)aSelector{
        NSString *selStr = NSStringFromSelector(aSelector);
        //companyName,则处理转发
        if ([selStr isEqualToString:@"companyName"]) {
            //返回处理这个转发的对象
            return self.companyModel;
        }else{
            return [super forwardingTargetForSelector:aSelector];
        }
    }
    

    3.- (void)forwardInvocation:(NSInvocation *)anInvocation
    这一步是 Runtime 最后一次给你挽救的机会,启用完整的消息转发机制,创建NSInvocation对象:(SEL、目标及参数).
    首先它会发送 -methodSignatureForSelector: 消息获得函数的参数和返回值类型如果返回了一个函数签名,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象,
    触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象.

    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSMethodSignature *sig = nil;
        NSString *selStr = NSStringFromSelector(aSelector);
        //判断你要转发的SEL
        if ([selStr isEqualToString:@"deptName"]) {
            //此处返回的sig是方法forwardInvocation的参数anInvocation中的methodSignature
            //为你的转发方法手动生成签名
            sig = [self.companyModel methodSignatureForSelector:@selector(deptName:)];
        }else{
            sig = [super methodSignatureForSelector:aSelector];
        }
        return sig;
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        
        NSString *selStr = NSStringFromSelector(anInvocation.selector);
        if ([selStr isEqualToString:@"deptName"]) {
            //设置处理转发的对象
            [anInvocation setTarget:self.companyModel];
            //设置转发对象要用的方法
            [anInvocation setSelector:@selector(deptName:)];
            BOOL hasCompanyName = YES;
            //第一个和第二个参数是target和sel
            [anInvocation setArgument:&hasCompanyName atIndex:2];
            [anInvocation retainArguments];
            [anInvocation invoke];
        }else{
            [super forwardInvocation:anInvocation];
        }
    }
    

    细节:
    resolveInstanceMethod 此函数会调用俩次。
    第一次:先走方法 _objc_msgSend_uncached,然后走方法 lookUpImpOrForward,再走方法 _class_resolveInstanceMethod,从这个大致的流程可以知道,这个流程,就是上面所分析的流程,寻找 imp的过程,没有找到就动态解析
    第二次:消息转发流程


    image.png

    相关文章

      网友评论

          本文标题:runtime消息传递与转发

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