小码哥底层原理笔记:Runtime之Method

我们接下来看看类对象的本质,其实就是下面这个结构体:

struct objc_class : objc_object {
    Class isa;//这个isa指针本来是在objc_object里面的,现在把它拿上来这里
    Class superclass;//指向父类的指针
    cache_t cache;             // 方法缓存
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

最后一个bits存储着非常多的东西,跟之前说的位域一样,想要取出某些东西必须bits&XX_MASK掩码。比如我们要取出这个类对象里面存储的data数据class_rw_t,则必须bits&FAST_DATA_MASK得到class_rw_t。

struct class_rw_t {//re=read write可读写的
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;//ro=read only是只读的,无法修改的

    method_array_t methods;//方法列表,如果是元类对象,那么放的就是类方法
    property_array_t properties;//属性列表
    protocol_array_t protocols;//协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

这个class_rw_t是可读写的,我们runtime运行时可以动态读写里面的信息,比如增加协议,方法,属性等。
class_ro_t的结构如下:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;//类名
    method_list_t * baseMethodList;//方法列表
    protocol_list_t * baseProtocols;//协议列表
    const ivar_list_t * ivars;//成员变量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
OC类对象的底层结构

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

比如methods里面存放的是method_list_t数组,method_list_t数组里面装的才是method_t

class_ro_t里面存放的baseMethodList、baseProtocols、ivars是类里面本身初始化的方法、协议和属性不包括分类的,这些是一维数组,是不可更改的。

当我们的程序运行起来的时候系统会将class_ro_t里面的方法生成一个method_list_t数组放到class_rw_t的methods里面,并且分类里面的方法数组也会生成一个method_list_t合并到class_rw_t的methods里面,我们运行时动态修改方法就是只能修改class_rw_t里面的方法。

iOS方法的内部实现实际上是这样:

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

SEL name函数名其实就是@selector(test),说白了这个就是函数名字符串
const char *types编码包含了函数的返回值类型和参数类型,比如以下test方法:

- (void) test{}

它的types是v16@0:8,v表示返回值类型是void,@表示id类型,:代表SEL类型,每个OC方法底层默认都会自带两个参数-(void)test:(id)self _cmd:(SEL)_cmd{}

方法缓存 cache_t cache

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即@selector(test)
    IMP _imp;//函数的内存地址
}

当我们在散列表找方法时,我们拿@selector(test) & _mask得到对应的索引,然后拿函数地址与对应索引的函数地址比较,如果不对,那么_mask减1,如果_mask为0,则_mask就赋值为散列表的长度继续找。如果散列表扩容了,那么编译器会清空缓存,即清空散列表,然后再重新存取。

OC方法执行流程:

OC调用一个方法实质就是通过objc_msgSend方法向这个对象发送一条消息。主要有三大流程:消息查找发送、动态方法解析、消息转发
消息发送

消息发送

动态方法解析
动态方法解析

消息转发
消息转发

方法替换

你可能感兴趣的:(小码哥底层原理笔记:Runtime之Method)