Runtime的本质(二)objc_class结构

这篇文章,我们主要来介绍一下objc_class结构的内容与含义。

我们知道Class的类型是objc_class类型
typedef struct objc_class *Class;
点进去objc_class可以看到部分定义:

在这里插入图片描述

objc_class继承objc_object
objc_object的部分定义:

在这里插入图片描述

简化可归结objc_class的结构为:

在这里插入图片描述

可以看出:
Class里面有isa、superclass指针,方法列表、属性列表、协议列表以及成员变量列表以及其他信息。
其中,方法列表包含了自己的方法列表以及分类的方法列表,且分类方法列表在原类方法列表前面。

通过操作objc_class里面的bits与上一个定义好的数值,就可以找到class_rw_t里面的内容。这种做法我们在上一篇文章中已经讲解过,用到的是位运算操作,这里不再做叙述。

其中:
class_rw_t中的rw是read和write的意思,也就是class_rw_t里面的内容是可读可写的。
class_ro_t中的ro是read和only read的意思,也就是class_ro_t里面的内容是只读的。

通过iOS大神MJ老师写的文件MJClassInfo.h,我们可以窥探class的具体内容。

导入文件,注意,Add to targets不要选中
在这里插入图片描述

YZPerson *person = [[YZPerson alloc] init];
mj_objc_class *cls = (__bridge struct mj_objc_class *)([YZPerson class]);
class_rw_t *data = cls->data();

img

好强大的工具,给MJ点赞

在class_rw_t存储的方法列表、属性列表、协议列表都是二维数组。

以方法列表为例:
method_array_t里面是method_list_t;method_list_t里面是结构体method_t

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; }
    };
};

在源码中可以看出:

在这里插入图片描述

在这里插入图片描述

img

而class_ro_t里面也有方法列表

在这里插入图片描述

参考源码,可以得出:
原始的class_ro_t中的方法列表与分类中的方法列表结合,放到class_rw_t的方法列表中。

更多学习:
Runtime源代码解读4(方法列表)
这是个大佬

method_t分析

method_t是对方法/函数的封装。
通过查看源码,可以看到method_t结构的组成:

struct method_t {
    SEL name;//函数名
    const char *types;//编码(返回值类型、参数类型)
    IMP imp;//指向函数的指针(函数地址)
};

其中,IMP代表函数的具体实现,imp是指向函数地址的指针,通过IMP可以真正的找到函数的入口。

SEL代表方法/函数名,一般叫做选择器,底层结构跟char *类似。
可以通过@selector()和sel_registerName()获得
可以通过sel_getName()和NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的

types是一个包含了函数的返回值类型、参数类型的编码字符串
其属于Type Encoding类型编码
iOS提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码。
对应含义可以在官方文档查看,这里仅列出一部分:
在这里插入图片描述

我们需要了解的是:
可以通过method_t里面的types获取到函数的返回值类型以及参数类型。

方法缓存

在objc_class里面有一个cache_t类型的cache,用散列表来缓存曾经调用过的方法,可以提高方法的查找速度。
其中,cache_t的组成为:

struct cache_t {
    struct bucket_t *_buckets;//散列表
    mask_t _mask;//散列长度-1
    mask_t _occupied;//已经缓存的方法数量
}

struct bucket_t {
    cache_key_t _key;//SEL作为key
    IMP _imp;//函数的内存地址
};

可以看出,用方法名作为key,找函数内存地址,形成一个散列表。

举个具体例子分析:

YZPerson *person = [[YZPerson alloc] init];
[person run];

方法缓存过程:

第一次调用某个对象方法的时候,会去该类的类对象cache中通过key-value方法查找,key是方法名,查找对应的IMP。
如果查找不到,会通过isa指针去其类对象里面查找该方法,有的话就通过散列表缓存到cache中并且调用该方法。
如果该类对象里面没有该方法,则去其父类里面找,也是先去缓存列表中找,找到就放入到自己的缓存中并调用。缓存中没有则去方法列表中找,找到会通过散列表缓存到自己cache中并且调用,没找到会继续去父类中找,直到找不到。第二次调用,去cache中查找,有,直接调用,没有继续按上面的方法做。

原文地址:https://www.cnblogs.com/r360/p/15812447.html