美文网首页数据结构iOS进阶OC基础
OC-Runtime-Class结构和OC消息机制

OC-Runtime-Class结构和OC消息机制

作者: xiaoyouPrince | 来源:发表于2020-07-09 21:25 被阅读0次

    OC - Runtime - Class 结构 和 OC 消息机制

    Runtime 源码中 Class 结构如下:

    // Class 其实就是一个 struct objc_class *
    typedef struct objc_class *Class;
    
    // struct objc_class 继承 objc_object
    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    }
    
    // objc_object 结构如下
    struct objc_object {
        isa_t isa;
    }
    

    所以Class本身结构如下:

    struct objc_class {
        isa_t isa;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    }
    

    Class 内部 (bits & FAST_DATA_MASK) 可以得到 (class_rw_t *) 类型,
    其内部结构如下

    struct class_rw_t {
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro;
    
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    }
    

    其中结构 class_ro_t 结构如下:

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
        // strong修饰的ivars
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
        
        // weak修饰的ivars
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    };
    

    这几个类型关系如下图

    Class

    class_rw_t 和 class_ro_t

    class_rw_t 里面的 methods、properties、protocols 是二维数组、是可读可写的,包含了类的初始内容,分类的内容等


    class_rw_t

    class_ro_t 里面同样也包含了 baseMethodList、baseProtocols、baseProperties 信息,他们是一维数组,是不可读写的,他们包含的是 Class 最原始的方法列表信息


    class_ro_t

    method_t

    在 class_rw_t 和 class_ro_t 中都包含 class 的各种方法协议信息,以方法为例,其类型为 method_t.

    • method_t 是对方法/函数的封装,结构如下
    struct method_t {
        SEL name;               // 函数名
        const char *types;      // 编码(返回值类型,参数类型)
        IMP imp;                // 指向函数的指针(函数地址)
    }
    
    • IMP 是指向函数具体实现的指针。定义id (*IMP)(id, SEL, ...)

    在 OC 底层的方法调用,都会转化为 C 语言的函数调用,所有的函数都有两个默认的参数 self、SEL 其他参数才是用户定义方法设置的参数,这也是在对象方法内我们能直接使用 self 的原因。

    • SEL 代表方法、函数名,定义typedef struct objc_selector *SEL;,通常叫做选择器,底层结构和 char* 类似。可以通过 @selector()sel_registerName 获得SEL

    type encode

    runtime 会对函数的返回值参数进行编码。具体来说,OC 对象的方法,在Runtime底层会转化为id (*IMP)(id, SEL, ...)类型指针。

    以get方法为例- (NSString *)name; 其编码为@16@0:8
    以无返回值方法为例- (void)name;其编码为v16@0:8

    以上两个方法对应的转换为IMP函数:NSString* name(id self, SEL _cmd)void name(id self, SEL _cmd) 其编码都是一样的。

    编码的规则示例如下:

    type encode

    官方说明: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

    方法缓存 cache_t

    Class 内部结构有个方法缓存cache_t, 用散列表来缓存曾经调用过的方法,可以提高查找速度。

    cache_t

    散列表有一个起始长度值,会使用使用到的@selector(sel) & _mask来缓存具体的方法地址。

    当列表的容量不够的时候会扩容,直接扩大为原来的2倍。

    objc_msgSend执行流程

    消息发送机制的执行流程主要分为3大阶段

    1. 消息发送阶段 -> 对象方法在Class中找,类方法在 MetaClass 中找,一层层往上找
    2. 动态方法解析 -> 系统找不到方法,会给一个机会添加动态方法
    3. 消息转发,如果此步再没有操作,就会报错 unrecognized selector sent to instance

    objc_msgSend 函数在 Runtime 的代码中是直接使用混编语言实现的,可以从源码中查到其查找方法流程: 缓存 -> 自己的方法列表 -> 遍历父类的缓存 & 方法列表。整个流程如果都没有,就进入查找动态方法阶段。

    impLookupOrForword

    如果都没找到,进入动态方法解析,动态方法解析流程如下。动态方法会根据 + (BOOL)resolveInstanceMethod:(SEL)sel; 查看内部有没有给对应的 selector 动态添加方法实现,如果有就重新走消息发送流程。否则进入下一阶段:消息转发。

    动态解析

    如果动态解析还是什么都没有操作,就会进入消息转发阶段。消息转发阶段流程如下,这里是闭源的无法从源码上查看,但是可以从代码角度验证流程。


    消息转发
    #pragma mark -  消息转发阶段 - 1
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(test)) {
            return [NSObject objectSpecifier];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    
    #pragma mark -  消息转发阶段 - 2
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        // 这里如果没有操作就会走找不到方法。
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        // 对 invocation 做需要做的事情
    }
    

    面试题

    • 简述 OC 对象的消息机制。
    1. OC 中的方法调用其实都是转化成了 objc_msgSend 函数的调用,给receiver发送一条消息
    2. objc_msgSend 函数内部有3大阶段。
        1. 消息查找阶段
        2. 动态消息解析阶段
        3. 消息转发阶段
    
    • 消息转发机制流程
    1. 调用 - (id)forwardingTargetForSelector:(SEL)aSelector 方法,如果有实现,且返回转发的对象,就将消息转发给该对象。反之,进入第二步骤
    2. 调用 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法,如过有针对 aSelector 的方法签名就继续调用 - (void)forwardInvocation:(NSInvocation *)anInvocation 方法去实现任何自己想实现的逻辑。反之进入第三步
    3. 调用 doesNotRecognizeedSelector 方法,向外抛出异常,经典的 unrecognized selector sent to instance 错误
    
    • Runtime 是什么,项目中用到过吗?
    1. 绑定对象
    2. 针对 unrecongnized selector 方法报错,可以在第三阶段,消息转发阶段进行拦截,自己处理没有实现的方法,不让系统崩溃
    

    下面代码输出什么?

    @implementation Son : Father
    - (id)init {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    @end
    
    // 答案: 都是 Son
    // 原因: super 是编译器特性,会被转成 objc_msgSendSuper 函数,其真实接收者仍是 self,class 方法是在 NSObject 中实现的,最终在消息查找会走到基类的 class 方法,返回的是消息接收者的类型,即 self 的类型。
    
    • super 关键字详解
    一个Person 类,其 init 方法如下:
    - (instancetype)init
    {
        self = [super init];
        return self;
    }
    
    // 使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m -o Person-arm64.cpp 转化为 C++ 代码如下
    
    static instancetype _I_Person_init(Person * self, SEL _cmd) {
        self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
        return self;
    }
    
    // [super init]; 简化如下
    struct __rw_objc_super { 
        struct objc_object *object;        // 真正的消息接受者 
        struct objc_object *superClass;    // 接收者父类,作为第一个查找的类。
    };
    
    // __rw_objc_super 结构体
    __rw_objc_super objc_super = (__rw_objc_super){
        (id)self,
        (id)class_getSuperclass(objc_getClass("Person"))
    };
        
    // 实际上转化为此类型函数指针 (Person *(*)(__rw_objc_super *, SEL))
    objc_msgSendSuper(objc_super, sel_registerName("init"));
    

    super 关键字总结

    1. super 关键字会转化为 objc_msgSendSuper(),函数.
    2. 其中会指定,消息接受者,从哪个类开始查找,和要发送的消息。
    3. super 就是要在当前对象的父类开始查找消息的实现。

    --- end ---

    相关文章

      网友评论

        本文标题:OC-Runtime-Class结构和OC消息机制

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