美文网首页
关于iOS中Runtime的理解

关于iOS中Runtime的理解

作者: 程序后媛团 | 来源:发表于2018-02-05 18:58 被阅读0次

    虽说苹果公司早就公开了Objective-C的Runtime源码,然而我却并没有深入了解过Runtime,实在汗颜,于是趁着不太忙的时候自己结合着网上的一些相关文章研究一下,然后记录自己对Runtime的理解。鉴于本人能力有限,自己的理解如有误,还望大家能够指正。(说明一下:文中的代码,左右滑动即可查看全部代码内容)

    runtime源码地址,我这里引用的代码版本是objc4-709。众所周知,Objective-C是面向对象编程的语言,那么在Objective-C的世界观里面,万物皆对象,包括我们熟知的Class。

    对象(id)和类(Class)以及元类(MetaClass)的关系

    我们首先来看一看Runtime中Class的定义:

    从上面的代码可以看出Class的本质就是一个指向objc_class结构体的指针。

    我们再看一下Object的定义:

    从上面的代码可以看出id的本质就是一个指向objc_object结构体的指针。

    结合Class和id的定义,我们发现在objc_class结构体里面,有指向其所属类的isa指针,这就意味着,其实我们所讲到的Class也是一个对象,既然类也是对象,那么类对象所属的类是什么呢?于是就出现了元类MetaClass,而元类所属的类就是根元类NSObject MetaClass。为了能自圆其说,规定根元类是最终的元类,它所属的类就是它本身,从而能够形成闭环。

    实例(id)、类(Class)、元类(MetaClass)关系图:

    关系图佐证代码:

    void test (){

       // 继承关系 Man : Person : NSObject

       Person *p = [[Person alloc] init];

       Man *m = [[Man alloc] init];

       /* isa */

       // 对象p的所属类就是Person类对象(Person Class)

       Class cls = object_getClass(p);

       BOOL clsIsMeta = class_isMetaClass(cls);

       NSLog(@"cls == %@ clsIsMeta = %@ ",cls, @(clsIsMeta));

       // Person类对象的所属类就是Person元类(Person MetaClass)

       Class meta = object_getClass(cls);

       BOOL metaIsMeta = class_isMetaClass(meta);

       NSLog(@"meta == %@ metaIsMeta = %@",meta, @(metaIsMeta));

       // Person元类的所属类就是根元类(NSObject MetaClass)

       Class meta_meta = object_getClass(meta);

       BOOL meta_meta_isMeta = class_isMetaClass(meta_meta);

       NSLog(@"meta_meta == %@ meta_metaIsMeta = %@",meta_meta, @(meta_meta_isMeta));

       // 根元类的所属类还是它本身,形成闭环

       Class meta_meta_meta = object_getClass(meta_meta);

       BOOL meta_meta_meta_isMeta = class_isMetaClass(meta_meta_meta);

       NSLog(@"meta_meta_meta == %@ meta_meta_meta_isMeta = %@",meta_meta_meta, @(meta_meta_meta_isMeta));

       /* super class */

       // Man Class的父类是Person Class

       Class mSuperCls = [m superclass];

       BOOL mSuperClsIsMeta = class_isMetaClass(mSuperCls);

       NSLog(@"mSuperCls = %@ mSuperClsIsMeta = %@",mSuperCls, @(mSuperClsIsMeta));

       // Person Class的父类是NSObject Class

       Class pSuperCls = [mSuperCls superclass];

       BOOL pSuperClsIsMeta = class_isMetaClass(pSuperCls);

       NSLog(@"pSuperCls = %@ pSuperClsIsMeta = %@",pSuperCls, @(pSuperClsIsMeta));

       // 根元类的父类是NSObject Class

       Class rootClass = [meta_meta superclass];

       const char *rootClsName = class_getName(rootClass);

       BOOL rootClassIsMeta = class_isMetaClass(rootClass);

       NSLog(@"rootClass == %@ rootClsName = %s rootClassIsMeta = %@",rootClass,rootClsName,@(rootClassIsMeta));

       // NSObject Class的父类是nil

       Class nSuperCls = [pSuperCls superclass];

       BOOL nSuperClsIsMeta = class_isMetaClass(nSuperCls);

       NSLog(@"nSuperCls = %@ nSuperClsIsMeta = %@",nSuperCls, @(nSuperClsIsMeta));

    上述代码运行结果如下:

    Messaging

    Runtime的核心就是消息传递,Objective-c中的方法调用都是通过Runtime的消息传递实现的。

    整个消息传递的流程:Runtime用objc_msgSend(p, @selector(say:))的方式将say:消息发送给p实例,objc_msgSend函数通过p的isa找到它的Class(Person),在Class(Person)的method_list查找say:,如果没有找到say:,继续往superclass中查找,一旦找到say:,就会执行say:的实现IMP。

    消息发送时为了提高查表效率,Runtime建立了缓存机制,消息分发时,会在struct objc_cache \*cache中匹配,如果没有会查方法列表,找到后会加入到缓存中(method_name作为Key,method_imp作为value)。

    若果上述流程走完,仍未找到相应的方法,程序一般会抛出异常(unrecognized selector send to …..),但是抛出异常前Runtime会给出三次拯救程序的机会:

    1.Method resolution

    2.Fast forwarding

    3.Normal forwarding

    正常的发送流程:

    objc_msgSend函数将say:消息发送给p实例,找到say:的实现,执行。

    Objective-C代码调用转化为Runtime:

    如果正常的消息发送流程出现问题则会依次走下面的逻辑:

    Method resolution:

    OC运行时会调用+ (BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel,如果添加了函数并返回YES,运行时会重新启动一次消息发送的过程。

    关于class_addMethod(Class cls, SEL name, IMP imp, const char *types)中的types参数(Type Encodings)

    Fast forwarding:

    如果目标实现了- (id)forwardingTargetForSelector:(SEL)aSelector,Runtime会调用这个方法,把这个消息转发给其他对象的机会。这个方法只要返回的不是nil和self,整个消息发送过程就会重启,发送的对象变成你返回的那个对象,否则,继续Nromal forwarding。

    Normal forwarding:

    这是Runtime给的最后一次挽救程序的机会,首先会发送- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector消息获得函数的参数和返回值类型。如果返回nil,Runtime则会发出- (void)doesNotRecognizeSelector:(SEL)aSelector消息,这是程序就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送- (void)forwardInvocation:(NSInvocation *)anInvocation消息给目标对象。

    NSInvocation实际上就是对一个消息的描述,包括selector以及参数等信息。所以可以在- (void)forwardInvocation:里修改传进来的NSInvocation对象,然后发送- (void)invokeWithTarget:消息给它,传进去一个新的目标:

    直接调用msgSend函数发送消息:

    //中定义的objc_msgSned,并未明确参数列表和返回值类型,要转化下面的代码才能使用

    ((void(*)(id, SEL))objc_msgSend)

    //然后直接调用发送消息

    ((void(*)(id, SEL))objc_msgSend)(p,@selector(say:));

    绕过消息发送流程,直接调用:

    // 定义一个名为saySomething的函数指针

    void(*saySomething)(id, SEL,NSString*);

    // 将指针指向已有OC方法say:的实现

    saySomething = (void(*)(id, SEL,NSString*))[p methodForSelector:@selector(say:)];

    // 调用函数

    saySomething(p,@selector(say:),@"hello");

    (完结,以下无正文)

    相关文章

      网友评论

          本文标题:关于iOS中Runtime的理解

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