前言:
OC属于一门动态性的语言,一般语言编译流程是:编写代码->编译链接->运行代码。而OC因为有runtime机制,在运行代码的时候对编译链接动态改变。
isa指针
arm64之后,我们是无法直接获取到一个对象的isa地址,是因为class中的isa使用了一个union来储存isa还有其他的一些信息,优化内存的使用率。通过源码 我们可以看到isa的一些信息。
/* 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 内部结构
当类运行时的时候,runtime机制会将储存在ro(只读)内部的class方法合并到method_array_t methods中去,便于动态添加方法。协议,属性亦是如此。
窥探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,然后就去对应的散列表中取值,因为赋值的时候顺序都是排列好的,所以取值的时候就比较方便了,提高效率,这样比遍历数组效率更高,更有效。