oc中weak
指针主要用于打破循环或者防止循环引用的发生,应用场景仍是很普遍的。那么被weak
修饰的指针与被指向的对象在底层的运做机制究竟怎样的呢?为何在对象释放销毁时weak
指针能自动置为nil
,从而避免了野指针的错误?算法
当对象被一个weak
指针引用时,底层的实现原理就是:不对被引用的对象进行retain
,而是利用哈希表对weak
指针与被指向的对象进行标记、关联。当对象销毁释放内存管时经过以前的标记对weak
指针地址进行查找,最后把weak
指针的指向置为nil
。数组
首先weak
实现的函数调用顺序如图 bash
weak
指针标记须要处理的对象顺序以下图所示:
关于SideTable
以及根据对象指针查找对应SideTable
的分析在以前分析对象引用计数的文章中有相关的说明,这里就再也不重复了。这里主要分析下处理weak_entry_t
结构体的函数app
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) { //if 判断 是否把weak指针指向的对象是否已经有对应的entry,有的话表明对象之前有被弱引用指向
append_referrer(entry, referrer); //把 referrer 存进 对应的 entry
}
else { //对象第一次被弱指针引用
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table); //weak_table.weak_entries 扩容处理
weak_entry_insert(weak_table, &new_entry); //吧 weak_entry_t 放入 weak_table.weak_entries中数组,经过referent的哈希运算&mask获得索引
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
复制代码
这里的逻辑主要是经过判断查找对象对应SideTable
的weak_table
属性中是否包含有对应的weak_entry_t
结构体,查找的实现主要是经过把对象指针通过必定的哈希算法运算后与上一个特定的数值(该数值一般是要查找顺序表的最大索引)后得出的索引,再根据索引查找到特定的值与要查找的值对比得出结果。具体逻辑能够从源码中看weak_entry_for_referent
这个函数的实现。ide
上面的else
语句就是处理对象第一次被弱引用指向的处理。经过生成一个weak_entry_t
结构体后,把其插入到weak_table
中,插入的逻辑与上面判断是否含有特定weak_entry_t
的逻辑都是同样的,经过哈希算法得到因此,而后插入相应的索引位置,这里还有一个对weak_table
进行扩容的处理weak_grow_maybe(weak_table);
主要是判断weak_table
的存放对象容量大于或等于总容量的3/4
时对weak_table
的进行两倍容量的扩容后,再把旧数据复制到扩容后的内存中。函数
这种状况就相对复杂一点,首先咱们看下最终存放weak
指针地址的结构体定义源码分析
struct weak_entry_t {
//被引用的对象指针(通过包装处理)
DisguisedPtr<objc_object> referent;
//联合体,用于存放weak指针地址
union {
struct {
weak_referrer_t *referrers; //8字节 当inline_referrers不够存放数据的时候,使用该指针在堆上开辟空间存放数据
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;//8字节
uintptr_t mask;//8字节
uintptr_t max_hash_displacement;//8字节
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; // 32字节
};
};
}
复制代码
weak_entry_t
结构体主要是经过上面的联合体(union
)来存放对象的weak
指针地址。代码中的联合体分为两部分,内存大小为64个字节,在添加weak
指针的时候优先使用的是inline_referrers
数组存放,若是数组的已经存满了数据(4个指针),就会用上面的结构体的数组指针来在堆上开辟空间来存放数据,这样就能够存放较多的weak
指针地址。post
若是对象有被weak
指针指向的话,在对象销毁释放内存执行-dealloc
方法的时候,根据函数的调用顺序会执行到weak_clear_no_lock
这个方法。咱们来看下次方法是怎样使weakPointer = nil
ui
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
}
}
}
weak_entry_remove(weak_table, entry);
}
复制代码
能够看出上面代码主要是获取到对应对象的weak_entry_t *entry
,根据其内部结构体获取到存放的weak
指针地址的个数,而后遍历存放的weak
指针的内存,把weak
指针置为nil
。spa