1、__weak修饰符的使用案例
在开发的过程中,可能回遇到循环引用的问题。所谓循环引用,当对象A持有了对象B,与此同时对象B同时也持有对象A时,此时对象A销毁需要对象B先销毁,而对象B销毁同样需要对象A先销毁,就导致相互等待销毁,此时对象A,对象B的引用计数都不为0,所以对象A,对象B此时都无法释放。 从而导致了内存泄漏。
解决循环引用最常用的方法就是使用__weak修饰符。
- (void)weakSelf{
self.name = @"jack";
//使用__weak修饰符后,若引用self,self的引用计数不会增加。
__weak typeof(self) weakSelf = self;
void(^block)(void) = ^{
NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
};
block();
}
2、weak引用的流程图
weak断点.png
对
__weak打断点,查看相关的汇编代码,可以看到接下来要跳转的方法为objc_initWeak。在源码中查找该方法,该方法在NSObject.mm文件下。
方法:id objc_initWeak(id *location, id newObj)
-
id *location:弱应用修饰的对象的位置 -
id newObj:被若引用的对象
weak的相关底层方法:
weak相关底层方法.png
从这些方法可以看出,弱引用是存储在sideTable中。sideTable具体内容这里不进行说明,只展示相关的数据结构,以便于后续的阐述。
sideTable的结构体.png
(1). 使用新的obj创建一个sideTable:SideTable * newTable = &SideTables()[newObj];
*a、使用新值的指针,获得以 newObj 为索引所存储的值地址。通过hash找到newObj所在的SideTable使用weak引者的地址---disguise_ptr(oldObj),在SideTables中,查找到改地址对应的sideTable
(2). 使用原有的obj获取到对应的sideTable:oldTable = &SideTables()[oldObj];
*a、使用weak引者的地址---disguise_ptr(oldObj),在SideTables中,查找到改地址对应的sideTable
(3). 使用weak_table和referent来查找相关的entry:
static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
*a、通过weak_table获取到weak_entries(weak_table->weak_entries)
*b、遍历weak_entries,查找可以匹配referent的entry。[这里是通过对referent的哈希运算,并与weak_table->mask进行进行与操作,获取到index(与mask的与操作,可以有效的防止越界操作),在index位置没有匹配的referent,会自动向后匹配。以weak_table->max_hash_displacement为匹配的边界]
/**********************************************************************/
/*>>>>>计算出来entry在weak_table->weak_entries位置的index<<<<<*/
/**********************************************************************/
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
//weak_entries中取出index位置的referent,并不是要查询的被引用obj,继续循环
while (weak_table->weak_entries[index].referent != referent) {
//地址往后+1,并计算出来新的index
//这个与操作可以有效的防止index的越界
index = (index+1) & weak_table->mask;
//index和begin相等,即证明所有的entries已经遍历完成。
if (index == begin) bad_weak_table(weak_table->weak_entries);
//遍历次数超过了max_hash_displacement,直接返回nil
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
*c、查找逻辑
weak_entry_for_referent.png
(4). 从entry的引用中,清除掉referrer:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
*a、清除一个referrer,是遍历entry的referrers(entry->referrers[index]),匹配对应的referrer
*b、最重要的工作还是要查找referrer对应的index。[这里是通过对referrer的哈希运算,并与entry->mask进行进行与操作,获取到index(与mask的与操作,可以有效的防止越界操作),在index位置没有匹配的referrer,也是会进行后向匹配。以entry->max_hash_displacement为匹配的边界]
/**********************************************************************/
/*>>>>>计算出来referre在entry->referrers若引用位置的index<<<<<*/
/**********************************************************************/
//hash函数计算出来位置,
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
//从计算出来的位置开始查询
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
//已经超出了max_hash_displacement,报错objc_weak_error,并返回
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
*c、找到了对应的referrer以后,把查询到的index位置的指向置为nil,即从referrers中删除了改referrer。enry的num_refs--操作
/**********************************************************************/
/*>>>>>找到了需要清除的referrer的位置,进行删除操作,并且把引用计数减1<<<<<*/
/**********************************************************************/
//找到了,从referre中删除这个弱引用对象
entry->referrers[index] = nil;
//引用计数减1
entry->num_refs--;
*d、删除referrer操作
remove_referrer.png
(5). 删除entry的操作,即删除entry中referrers中为空的entry:
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
*a、当一个entry中,若引用对象的数量为0了,也就是referrers链表没有数据,这个entry就需要清除掉
*b、清除掉weak_table的一个entry后,需要处理weak_table的num_entries,进行减1操作
/**********************************************************************************/
/*>>>>>清除掉weak_table->entries中为空的entry,并且weak_table->num_entries减1操作<<<<<*/
/**********************************************************************************/
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
//压weak_entryr中的entries的空间
/*
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
*/
weak_compact_maybe(weak_table);
*c、weak_table中清除entry
weak_entry_remove.png
(6). 创建entry并插入到一个weak_table的entries中:
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
*a、当一个对象被弱引用,并且第一次被弱引用,首先要创建一个引用对象和被引用的对象entry
/*********************/
/*>>>>>新建entry<<<<<*/
/*********************/
//新建entry,
weak_entry_t new_entry(referent, referrer);
//判断entries是否需要扩容,需要的话,扩容原来尺寸的2倍
weak_grow_maybe(weak_table);
*b、把新建的entry插入到weak_table->weak_entries[和删除entry时一样的操作,第一步是要根据referent通过hash函数计算出来index(需要和weak_table->mask进行与操作,防止越界),查询index处是否有数据。有数据的话,后向查询下一个位置,直到找到对应的index。或者超出weak_table的最大存放数量]
/***************************************************************/
/*>>>>>计算新建的entry在weak_table->weak_entries的的位置index<<<<<*/
/**************************************************************/
//获取到weak_entries
weak_entry_t *weak_entries = weak_table->weak_entries;
//函数函数计算存储位置(理解为数组下标)
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
//查找到位index的位置,如果有数据,就继续查找下一个位置
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
*c、计算出来所在的位置后,进行插入操作,并且把weak_table->num_entries进行加1操作
//找到空位置index,把new_entery插入到index位置
weak_entries[index] = *new_entry;
//弱引用计数加1
weak_table->num_entries++;
*d、新建的entry插入到weak_table_entries中
weak_entry_insert.png
(7). 已经被弱引用的对象referent,新加增弱引用操作。即entry->referres新增一个对象:
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
*a、在已经被弱引用的前提下,再次被另一个对象进行弱应用,就要在entry->referres新增一个成员
*b、在插入新的referre之前,会先判断referrers是不是需要进行扩容。如需要扩容,扩充为原来的两倍
/********************/
/*>>>>>扩容的操作<<<<<*/
/********************/
//referrers的带下尺寸,可以看出,如果需要扩容的话,是按照old_size的两倍进行开辟空间的
size_t new_size = old_size ? old_size * 2 : 8;
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
*c、和其他的操作一样,还时必须要计算出来referrer所在的位置index
/******************************************************/
/*>>>>>计算新的referrer所在的index<<<<<*/
/*>>>>>w_hash_pointer(new_referrer) & (entry->mask);<<<<<*/
/******************************************************/
//计算出来在entry中的index位置
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
//查找index位置是不是有数据。有的话。就往后加一个位置,继续查询
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
*d、将referrer插入到计算出来的index处
//取到index的地址,进行referrer的存储
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
//弱引用指针个数加1
entry->num_refs++;
*e、将referrer插入到计算出来的index处
append_referrer.png
3、对象销毁时,weak引用的处理
当对象的引用计数为0的时候,或者调用了dealloc方法,runtime会调用_objc_rootDealloc。
对象销毁的弱引用流程.png
- 1、一个对象销毁的时候,会查询isa中的weakly_referenced参数,如果是1的话,需要清理弱引用。
- 2、清理的时候,使用weak_table和referent查找到entry
- 3、遍历entry中的referres,把里面的对象置为nil
- 4、清理完成referrers后,删除掉entry








网友评论