美文网首页
Objective-C Runtime

Objective-C Runtime

作者: lever_xu | 来源:发表于2018-07-19 14:16 被阅读0次

    本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机。主要内容如下:

    • 引言
    • 简介
    • Runtime 基础数据结构
    • 消息
    • 动态方法解析
    • 消息转发
    • 健壮的实例变量 (Non Fragile ivars)
    • Objective-C Associated Objects
    • Method Swizzling
    • 总结

    引言

    [receiver message] 会被编译器转化为:

    objc_msgSend(receiver, selector)
    

    如果消息含有参数,则为:

    objc_msgSend(receiver, selector, arg1, arg2, ...)
    

    执行以上代码会出现以下几种情况:

    • 消息的接收者能够找到对应的 selector,直接执行了接收者这个对象的特定方法;
    • 消息被转发;
    • 临时向接收者动态添加这个 selector 对应的实现内容;
    • 代码崩溃。

    在编译阶段确定了要向接受者发送message这条消息,而 receive 将要如何响应这条消息,需要根据运行时发生的情况来决定。

    Runtime 基础数据结构

    objc_msgSend:方法,它的真身是这样的:

    id objc_msgSend ( id self, SEL op, ... );
    

    id

    objc_msgSend 第一个参数类型为id,它是一个指向类实例的指针

    typedef struct objc_object *id;
    
    struct objc_object {
    private:
       isa_t isa;
    
    public:
    
       // ISA() assumes this is NOT a tagged pointer object
       Class ISA();
    
       // getIsa() allows this to be a tagged pointer object
       Class getIsa();
    }
    

    objc_object 结构体包含一个 isa 指针,类型为 isa_t 联合体。根据 isa 就可以顺藤摸瓜找到对象所属的类。isa 这里还涉及到 tagged pointer 等概念。因为 isa_t 使用 union 实现,所以可能表示多种形态,既可以当成是指针,也可以存储标志位。
    PS: isa 指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用 class 方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的 isa 指针指向一个中间类而不是真实的类,这是一种叫做 isa-swizzling 的技术.

    当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我画了一张示意图,如下所示:

    image.png

    Class

    Class 其实是一个指向 objc_class 结构体的指针:

    typedef struct objc_class *Class;
    

    而 objc_class 包含很多方法,主要都为围绕它的几个成员做文章:

    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
       class_rw_t *data() { 
           return bits.data();
       }
       ... 省略其他方法
    }
    

    因为类也是一个对象,那它也必须是另一个类的实列,这个类就是元类 (metaclass)。元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,直到一直找到继承链的头。

    元类 (metaclass) 也是一个对象,那么元类的 isa 指针又指向哪里呢?为了设计上的完整,所有的元类的 isa 指针都会指向一个根元类 (root metaclass)。根元类 (root metaclass) 本身的 isa 指针指向自己,这样就行成了一个闭环。上面提到,一个对象能够接收的消息列表是保存在它所对应的类中的。


    image.png

    有趣的是根元类的超类是 NSObject,而 isa 指向了自己,而 NSObject 的超类为 nil,也就是它没有超类。

    cache_t

    struct cache_t {
        struct bucket_t *_buckets;
        mask_t _mask;
        mask_t _occupied;
        ... 省略其他方法
    }
    

    _buckets 存储 IMP,_mask 和 _occupied 对应 vtable。
    cache 为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在isa指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了,而是优先在 cache 中查找。Runtime 系统会把被调用的方法存到 cache 中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。
    bucket_t 中存储了指针与 IMP 的键值对:

    struct bucket_t {
    private:
        cache_key_t _key;
        IMP _imp;
    
    public:
        inline cache_key_t key() const { return _key; }
        inline IMP imp() const { return (IMP)_imp; }
        inline void setKey(cache_key_t newKey) { _key = newKey; }
        inline void setImp(IMP newImp) { _imp = newImp; }
    
        void set(cache_key_t newKey, IMP newImp);
    };
    

    消息

    Objc 中发送消息是用中括号([])把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。

    objc_msgSend函数

    消息发送的步骤:
    1、检测这个selector是不是要忽略的。比如 Mac OS X开发,有了垃圾回收就不理会 retain, release 这些函数了。
    2、检测这个target 是不是 nil 对象。ObjC 的特性是允许对一个nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
    3、如果上面两个都过了,那就开始查找这个类的 IMP,先从cache里面找,完了找得到就跳到对应的函数去执行。
    4、如果 cache 找不到,就去类的方法列表中找。
    5、如果分发表找不到就到超类的方法列表去找,一直找,直到找到NSObject类为止。
    6、如果还找不到就要开始进入动态方法解析了,后面会提到。

    11.png

    方法中的隐藏参数

    objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

    • 接收消息的对象(也就是self指向的内容);
    • 方法选择器(_cmd指向的内容)。
      当方法中的super关键字接收到消息时,编译器会创建一个objc_super结构体:
    struct objc_super { id receiver; Class class; };
    
    面试题1
    @implementation Son : Father
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    
    @end
    

    以上代码输出结果是什么呢?
    使用 clang重写命令:

    clang -rewrite-objc Son.m
    

    重写后,代码如下所示

    static instancetype _I_Son_init(Son * self, SEL _cmd) {
        self = ((Son *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("init"));
        if (self) {
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_x__wh4dgr654mx__qc0b9bqyg5w0000gn_T_Son_f67def_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_x__wh4dgr654mx__qc0b9bqyg5w0000gn_T_Son_f67def_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"))));
        }
        return self;
    }
    
    [self class]  => objc_msgSend(self, sel_registerName("class"))
    

    把 self 做为第一个参数传递进去。

    [super class] => objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class")); 
    

    而在调用 [super class]时,会转化成 objc_msgSendSuper函数。看下函数定义:

    id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
    

    第一个参数是 objc_super 这样一个结构体,其定义如下:

    struct objc_super {
        /// Specifies an instance of a class.
        __unsafe_unretained _Nonnull id receiver;
    
        /// Specifies the particular superclass of the instance to message. 
    #if !defined(__cplusplus)  &&  !__OBJC2__
        /* For compatibility with old objc-runtime.h header */
        __unsafe_unretained _Nonnull Class class;
    #else
        __unsafe_unretained _Nonnull Class super_class;
    #endif
        /* super_class is the first class to search */
    };
    #endif
    

    结构体有两个成员,第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self 。第二个成员是记录当前类的父类是什么。
    所以,当调用 [self class] 时,实际先调用的是 objc_msgSend函数,第一个参数是 Son当前的这个实例,然后在 Son 这个类里面去找 - (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。
    objc Runtime开源代码对- (Class)class方法的实现:

    - (Class)class {
        return object_getClass(self);
    }
    

    而当调用 [super class]时,会转换成objc_msgSendSuper函数。第一步先构造 objc_super 结构体,结构体第一个成员就是 self 。第二个成员是 (id)class_getSuperclass(objc_getClass(“Son”)) , 实际该函数输出结果为 Father。第二步是去 Father这个类里去找- (Class)class,没有,然后去NSObject类去找,找到了。最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用,此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son。
    简而言之:因为super为编译器标示符,向super发送的消息被编译成objc_msgSendSuper,但仍以self作为reveiver。

    面试题2
    BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL res3 = [(id)[Father class] isKindOfClass:[Father class]];
    BOOL res4 = [(id)[Father class] isMemberOfClass:[Father class]];
    NSLog(@"%s %s %s %s",res1?"YES":"NO",res2?"YES":"NO",res3?"YES":"NO",res4?"YES":"NO");
    

    以上代码输出的结果是什么呢?
    我们使用clang -rewrite-objc main.m重写,可获得如下代码:

    BOOL res1 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class")), sel_registerName("isKindOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class")));
    BOOL res2 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class")), sel_registerName("isMemberOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class")));
    BOOL res3 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Father"), sel_registerName("class")), sel_registerName("isKindOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Father"), sel_registerName("class")));
    BOOL res4 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Father"), sel_registerName("class")), sel_registerName("isMemberOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Father"), sel_registerName("class")));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x__wh4dgr654mx__qc0b9bqyg5w0000gn_T_father_15bb14_mi_0,res1?"YES":"NO",res2?"YES":"NO",res3?"YES":"NO",res4?"YES":"NO");
    

    isKindOfClass的实现是通过比较isa指针是否指向同一个类(元类),如果不是就比较父类的isa指针,直到isa指向了NSObject的isa,如果还未相等就返回NO。
    isMemberOfClass的实现是通过比较isa指针是否指向同一个类(元类),如果相同就返回YES。

    关于Class之深入Class

    参看文章

    神经病院objc runtime入院考试
    Objective-C Runtime

    相关文章

      网友评论

          本文标题:Objective-C Runtime

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