runtime消息传递与转发

官方文档及资源地址
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消息传递与转发)