美文网首页
OC runtime 原理解析

OC runtime 原理解析

作者: 咖啡豆8888 | 来源:发表于2018-10-17 21:48 被阅读14次

前言:

OC属于一门动态性的语言,一般语言编译流程是:编写代码->编译链接->运行代码。而OC因为有runtime机制,在运行代码的时候对编译链接动态改变。

isa指针

arm64之后,我们是无法直接获取到一个对象的isa地址,是因为class中的isa使用了一个union来储存isa还有其他的一些信息,优化内存的使用率。通过源码 我们可以看到isa的一些信息。


窥探class结构
/* isa指针结构  */
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1; /* 0:代表普通指针,代表Class Meta-Class对象的内存地址; 1:代表优化过,使用位域存储更多的信息 */
        uintptr_t has_assoc         : 1;/* 是否设置过关联对象,如果没有释放会更快,有设置过则会变成1 */
        uintptr_t has_cxx_dtor      : 1;/* 是否有C++的析构函数(.cxx_destruct),如果没有释放会更快  */
        uintptr_t shiftcls          : 33; /* 储存这Class,Meta-Class的内存地址信息,因为处在第三位,所以获取到真正isa指针地址的时候,需要&一个mask值 */
        uintptr_t magic             : 6; /* 用于调试分辨对象是否完成初始化 */
        uintptr_t weakly_referenced : 1;/* 是否被弱引用过,如果没有释放更快 */
        uintptr_t deallocating      : 1;/* 对象是否正在释放 */
        uintptr_t has_sidetable_rc  : 1; /*里面储存的值使引用计数-1 */
        uintptr_t extra_rc          : 19;/* 引用技术区是否大于无法储存在isa中,如果为1,那么引用计数会储存在一个叫SideTable的类的属性中 */
    };
}

窥探class_rw_t 内部结构

class_rw_t内部结构.png

当类运行时的时候,runtime机制会将储存在ro(只读)内部的class方法合并到method_array_t methods中去,便于动态添加方法。协议,属性亦是如此。

窥探class_ro_t 内部结构

class_ro_t 内部结构

这里面的方法列表等,来源于初始化class的时候方法列表,通过runtime机制,会合并到class_rw_t(可读可写)中去,
所以class_rw_t中的methods包含class_ro_t中的methods,还包括通过runtime添加到class中去的method。

method_t 内部结构

从上面我们可以看出,存放函数放的是 数组method_list_t,和method_array_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; }
    };
};

方法缓存列表 cache_t

用散列表来缓存曾经调用过的方法,可以提高查找速度

struct cache_t {
    struct bucket_t *_buckets; //散列表
    mask_t _mask;  //散列表长度-1
    mask_t _occupied; //已经缓存的方法数量
}
struct bucket_t {
private:
    cache_key_t _key; //散列表的key (SEL作为key)
    IMP _imp; //函数实现内存地址
};

从上面我可以可以大致看出缓存列表中的结构:用一个散列表来从存储方法,函数的SEL选择器作为key,来寻找IMP的实现内存地址,进行方法的调用。 牺牲内存换时间
散列表(哈希表):底层结构就是数组。
存值> 刚开始会初始化一个mask初始值,当方法调用的是时候会将@selector(method),会将@selector(method) & mask 生成一个数值索引(index)放置散列表中的索引值得位置,如果是首次引入,会index前面的置空操作留出内存以供后面操作(哈希表中用内存换取时间的概念)。不同method&mask有可能会生成相同的index,此时会将index - 1 ,继续method&mask存值,如果有值,就继续-1,如果当index = 0,还没有空位,会将index赋值给mask,从最大的索引继续寻找空位,直至找到空位,如果还没有会将进行扩容操作。此时mask2,当前数组2,清空所有值,然后在继续前面操作,直到把值放进索引中。这就是哈希表的原理
取值> 取值的时候,依旧是会将 @selector(method) & mask生成的index,然后就去对应的散列表中取值,因为赋值的时候顺序都是排列好的,所以取值的时候就比较方便了,提高效率,这样比遍历数组效率更高,更有效。

缓存验证

相关文章

网友评论

      本文标题:OC runtime 原理解析

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