美文网首页Object
iOS Runtime学习笔记(一) - 基础学习

iOS Runtime学习笔记(一) - 基础学习

作者: brownfeng | 来源:发表于2018-03-19 16:21 被阅读83次

    iOS Runtime学习笔记

    Runtime就是运行时, 核心就是消息机制. 对OC的函数调用,是一个动态调用过程,只有在运行的时候runtime系统才能知道真正调用的哪一个函数(C语言在函数调用过程中, 编译时候就已经决定会调用哪个函数了).

    可以去下载runtime的源码: Apple官方Runtime源码

    iOS Runtime中实例对象和类的本质

    iOS 中实例对象的本质

    先摆出结论: OC是一门面向对象的编程语言, 在编译过程中, 编译器会将OC对象转化成结构体.

    我们去中的objc.h中找到:

    typedef struct objc_class *Class;
    typedef struct objc_object *id;
    
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    以上出现了我们常用的Class, id等关键字的定义.

    我们可以看到OC中实际的类Class, 会被编译成struct objc_class. 我们操作的类的对象实例是struct objc_object, 并且该结构体中有一个指针指向struct objc_class.

    iOS OC中类的本质

    OC对象的结构体中有一个Class指针能够理解, 因为要知道该对象是哪个类的对象.但是我们在objc-runtime-new.h中发现objc_class继承自objc_object的.

    struct objc_class : objc_object {
        // Class ISA; // 继承了
        Class superclass;
        ...
    

    runtime.h中, 我们看到OC类的结构体struct objc_class的具体定义

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    

    因此, 两个地方都告诉我们, OC中类Class中也有一个指针指向Class, 因此类Class本质上也是一个对象, 我们一般称为类对象, 这个指向的Class是就是元类(metaClass)的对象.

    当我们调用对象方法时候, 会通过对象中的Class指针找到对应的Class,然后调用实例方法,同理当我们调用类方法时候, 会通过Class中的Class指针找到对应的meta Class,然后调用meta Class 中的方法.

    OC一般会隐藏元类, 并且元类也是某个类的实例, 这个类我们一般称为根元类(root meta Class). 并且所有的元类的根元类都是一个, 并且根元类的元类是它自己. (实际中根元类是NSObject的元类)

    下面是一个非常有名的图, 分表表示 isa 指针 和 super class指针.

    对象-类-元类-根元类的关系

    用一个实例表示这个过程如下:

    NSString实例的isa指针链

    iOS 中OC方法调用的本质

    iOS方法的动态调用

    我们一般将OC中的方法调用称为消息发送, 具体格式是[receiver message].例如:

    NSMutableString *str = [[NSMutableString alloc] initWithString:@"hello"];
    [str appendString:@" world"];
    

    其中str就是receiver, appendString:就是message.

    message.h头文件中如下方法,这个方法是runtime的核心方法,

    void objc_msgSend(void /* id self, SEL op, ... */ )
    objc_msgSend(receiever, selector, arg1, arg2, ...)
    
    

    调用实例如下:

    objc_msgSend(str, @selector(appendString:), @" world");
    

    该消息方法为消息的动态绑定完成了以下工作:

    • 它会主动查找receiver的selector对应的方法实现IMP
    • 然后将参数传递给receiver object, 然后调用这IMP
    • 最后返回该方法的返回值

    为了使得objc_msgSend能完成通过selector查找receiver对应的IMP, 我们在上一节中提到的OC类和对象的结构就非常重要.

    通过上一节的内容,我们知道一个OC类和OC对象有会有一个isa指针,指向他们各自的Class, 同时OC类还有一个super指针指向父类.

    下图非常清晰的展示了这个指针链结构. 同时圆形表示对象object, 方形表示类class. 在object's class中, 会存储 <selector, address> 的键值对, 我们一般称为dispatch_table, 方便我们通过某个object的class查找selector对应的address(IMP)

    image

    具体过程就是通过isa指针找到对应的class struct, 然后在dispatch table里面查找selector对应的方法, 如果没有找到,那么通过super指针查找父类的dispatch table, 一直找下去, 直到NSObject类, 如果还没有找到,就调用NSObject的doesNotRecognizeSelector:方法, 然后报unrecognized selector错误.(实际中间还有消息转发等内容, 后面会讲到).

    当然在消息查找的过程中, 会使用一个cache来加速这个过程. 其中, 对象方法(instance method)会保存到类对象(class object)的method list. 类方法(class method)会保存到meta class的 method list.

    Selector, Message, Method的含义

    Selector: 表示method的name. 我们一般见到的alloc,init,setObject:forKey:等等.一般用SEL表示SEL aSelector = @selector(doSomething:) 或者SEL aSelector = NSSelectorFormString(@"doSomething:")

    Message: 消息是一个selector和参数一起被发送的给receiver

    Method: 方法是selector和 implementation的集合, implementation是一个函数指针(IMP).

    Method Signature: 表示一个method能够接受的参数类型和返回数据类型.

    消息转发, unrecognized selector的补救

    上一节中提到,对象通过消息这种机制查找struc objc中的方法地址.如果在整个isa/super指针链中找不到对应的selector-IMP, 那么说明该对象无法收到这个消息, 此时就会进入消息转发(message forwarding)的环节, 通过这种机制, 我们可以在消息转发的过程中告知对象如何处理该selector.

    消息转发分成两个阶段: 1. 动态方法解析(dynamic method resolution), 2. 完整消息转发机制(full forwarding mechanism).

    1. 动态方法解析

    主要是询问receiver所属的类, 看它是否能动态添加方法, 处理该unrecognized selector.涉及一下方法, 分别是实例方法和类方法:

    + (BOOL)resolveInstanceMethod:(SEL)selector;
    + (BOOL)resolveClassMethod:(SEL)selector;
    
    2. 完整的消息转发机制

    其实本阶段也分成两个阶段:

    1> 备份者机制(replacement receiver). 或者称为重定向

    2> 真正的消息转发机制.

    第一种情况,如果前面阶段都没有完成. 那么runtime会请求receiver的- (id)forwardingTargetForSelector:(SEL)selector方法,询问它是否有其他的receiver来帮助处理这个selector.

    第二种情况. 此时只能启用真正的消息转发机制。完整的消息转发机制是这样的:

    1. 首先通过-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector返回一个NSInvocation对象, 这个方法中生成的NSInvocation作用在于把尚未处理的那条message有关的全部细节封装于这个NSInvocation对象中。此对象中包含选择子(selector)、目标(target)及参数。
    1. 在返回的NSInvocation对象不为nil时,消息派发系统(message-dispatch system)将亲自触发- (void)forwardInvocation:(NSInvocation *)anInvocation,把message dispatch给目标对象。

    其中,有以下内容需要注意:

    实现此方法时,如果发现调用操作不应该由本类处理,则需要沿着继承体系,调用父类的同名方法,这样一来,继承体系中的每个类都有机会处理这个调用请求,直至rootClass,也就是NSObject类。如果最后调用了NSObject的类方法,那么该方法还会继而调用doesNotRecognizeSelector以抛出异常(unrecognized selector send to instance xxx),此异常表明选择子最终也未能得到处理。消息转发到此结束。

    具体的流程如下

    参考文献

    Apple官方Runtime开源源码

    iOS runtime探究(一): 从runtime开始理解面向对象的类到面向过程的结构体

    selector/Message/Method的关系

    相关文章

      网友评论

        本文标题:iOS Runtime学习笔记(一) - 基础学习

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