美文网首页
Runtime 探析

Runtime 探析

作者: 浮萍向北 | 来源:发表于2018-12-28 20:06 被阅读0次

    Runtime 简介

    因为Objective-C是一门动态语言,这意味着它不仅需要一个编译器,也还需要一个运行时的系统来动态的创建类和对象、进行消息转递和转发。这就是Objective-C Runtime 系统存在的意义,它是整个Objc 运行框架的一块基石。


    Runtime 基础数据结构

    在我们调用方法是系统会自动为我们转换成 Objc_magSend:函数

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

    id

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

    typedef struct objc_object *id;
    

    objc_object 是什么? 参考objc-private.h部分源码:

    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_t 使用 union 实现,所以可能表示多种形态,即可以当成多种形态 即可以当成指针 也可以存储标志位。
    PS: isa 指针不总是指向实例对象的所属类,不能依靠它来确定类型 而是应该用 class 方法来确定实例对象的类。KVO的实现机制就是将被观察者对象的 isa 指针指向了一个中间类而不是真实的类,这种叫做 isa—swizzling 的技术。KVO

    SEL

    Objc_magSend 函数第二个参数类型为 SEL , 它是 selector 在objc 中表示类型。selector 是方法选择器,可以理解区分方法的ID,而ID的数据结构SEL:

    typedef struct objc_selector *SEL
    

    @selector( ) 基本可以等同于C语言的函数指针,只不过C语言中可以把函数名直接赋给一个函数指针,而Objc 的类不能直接应用函数指针。这样只能做一个@selector语法来取 它的结果是一个SEL类型。

    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();
    }
    ... 省略其他方法
    }
    

    objc_class 继承于 objc_object,也就是说一个Objc 类本身同时也是一个对象,为了处理类和对象的关系,runtime库创建了一种叫做元类(Meta Class),类对象所属的类型就叫做元类,它用来表述对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象。而每个类仅有一个与之相关的元类。当你发出一个类似 [NSObject alloc] 的消息时,实际上是把这个消息发送给一个类对象,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。 所有的元类最终指向根元类为其超类。所有的元类方法列表都能够响应消息的类方法。当 [NSObject alloc] 这条消息发送给对象的时候 objc_Send() 会去它的元类里面去查找能够响应的消息的方法。

    class-diagram.jpg

    上图实线是 superclass 的指针 ,虚线是isa 指针。
    1.root class 其实就是NSObject NSObject 是没有超类的,所以 root class(class)和superrclass 指向 nil。
    2.每个Class都有一个 isa 指针 指向唯一的Meta Class。
    3. Root class(meta)的superclass指向Root class,也就是NSObject 形成一个回路。
    4.每个Meta class的isa指针都指向Root class (meta)。

    cache_t

    struct cache_t {
    struct bucket_t *_buckets; // 存储Method的链表
    mask_t _mask;             //  缓存的bucket的总数
    mask_t _occupied;         //  表明目前实际占用的缓存bucket的个数
    ... 省略其他方法
    }
    

    cache 为了方法的调用的性能进行了优化。每当实例对象接收到一个消息时,它不会直接在isa 指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了 而是在cache 中查找。Runtime 系统会把被调用方法存到 cache 中,下次查找的时候效率更高。

    class_data_bits_t

    objc_class 中复杂的是 bits class_data_bits_t 结构体所包含的信息太多了,主要包含 class_rw_t,retain/release/autorelease/retainCountalloc 等信息,很多存取方法也是围绕它展开。

    struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
    class_rw_t* data() {
       return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ... 省略其他方法
    }
    

    Ivar

    Ivar 是一种代表类种实例变量的类型。

    typedef struct ivar_t *Ivar;
    
    struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;
    
    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
    };
    

    objc_property_t

    objc_property_t 是表示Objective-C声明的属性的类型,其实际是指向objc_property结构体的指针,其定义如下:

     typedef struct objc_property *objc_property_t;
    

    objc_property_attribute_t

    objc_property_attribute_t 定义了属性的特性(attribute),它是一个结构体,定义如下:

    typedef struct {
    const char *name;           // 特性名
    const char *value;          // 特性值
    } 
    

    Method

    Method 是一种代表类中的某个方法的类型。

    typedef struct method_t *Method;
    
    struct method_t {
    SEL name;
    const char *types;
    IMP imp;
    
    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
    };
    

    方法名类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
    方法类型 types 是个char指针,其实存储着方法的参数类型和返回值类型。
    imp 指向了方法的实现,本质上是一个函数指针,后面会详细讲到。

    IMP

    IMP 定义:

    typedef void (*IMP)(void /* id, SEL, ... */ );
    

    它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。

    你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id 和 SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id 和 SEL 参数就能确定唯一的方法实现地址;反之亦然。

    消息实现

    objc_msgSend 函数

    在前面简单对 objc_msgSend 进行了一点介绍。看起来像是objc_msgSend 返回了数据,其实 objc_msgSend 从不返回数据而是你的方法被调用后返回的数据。消息发送过程:

    1.检测这个 selector 是不是要忽略的。
    2.检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
    3.如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
    4.如果 cache 找不到就找一下Class中的方法列表。
    5.如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
    6.如果还找不到就要开始进入消息转发。

    方法中的隐藏参数

    我们经常在方法中使用 self 关键字来引用实例本身,但没有想过为什么 self 就能取到调用当前方法的对象。其实self 的内容是在方法运行时被偷偷的动态转入的。

    objc_msgSend 找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数转递给方法实现,同时还将两个隐藏的参数:
    1.接收消息的对象 (self 指向的内容)
    2.方法选择器 (_cmd 指向的内容)

    在这两个参数中,self 更有用。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径。
    而当方法中的super关键字接收到消息时,编译器会创建一个objc_super结构体:

    struct objc_super { id receiver; Class class; };
    

    这个结构体指明了消息应该被传递给特定超类的定义。但receiver仍然是self本身,这点需要注意,因为当我们想通过[super class]获取超类时,编译器只是将指向selfid指针和class的SEL传递给了objc_msgSendSuper函数,因为只有在NSObject类才能找到class方法,然后class方法调用object_getClass(),接着调用objc_msgSend(objc_super->receiver, @selector(class)),传入的第一个参数是指向self的id指针,与调用[self class]相同,所以我们得到的永远都是self的类型。

    下面这道题考察的点就是上面所说的
    下面代码输出什么?🤔

     @implementation Son : Father 
     - (id)init {
    self = [super init];
    if (self)
    {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
    }
    @end
    

    引用文献:
    神经病院 Objective-C Runtime 入院第一天—— isa 和 Class
    玉令天下的博客-Objective-C Runtime

    相关文章

      网友评论

          本文标题:Runtime 探析

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