iOS 底层 内存管理之weak指针 - AlvinSunny/OC-TheUnderlying GitHub Wiki
先让我们来看看SideTable的结构图

weak指针底层实现源码分析
需要通过dealloc的内部实现,查看对象释放过程来了解;
目的是为了找到 weak_clear_no_lock函数 的实现
- 如果赶时间可以直接到c++文件
objc-weak.mm中搜索🔍weak_clear_no_lock函数
NSObject.mm
1. 第一步
- (void)dealloc {
_objc_rootDealloc(self);
}
2. 第二步
inline void objc_object::rootDealloc()
{
//是否是tagged pointer指针 是直接return,不需要释放
if (isTaggedPointer()) return;
if (fastpath(isa.nonpointer && //优化过的isa
!isa.weakly_referenced && //没有被弱引用指向过
!isa.has_assoc && //没有设置过关联对象
!isa.has_cxx_dtor && //没有用到析构函数
!isa.has_sidetable_rc)) //引用计数没有过大
{
assert(!sidetable_present()); //全局的引用计数散列表也没有
free(this); //直接释放
}
else {//优化过的isa,但其他条件不满足
object_dispose((id)this);
}
}
3. 第三步
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj); //销毁实例对象
free(obj);//释放
return nil;
}
4. 第四步
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor(); //是否有c++析构函数
bool assoc = obj->hasAssociatedObjects();//是否有设置关联对象
// This order is important.有就销毁它们
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
//开始回收内存
obj->clearDeallocating();
}
return obj;
}
5. 重点来了 😁😁😁😁😁😁😁😁😁
inline void objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {//没有被优化过的ISA
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
/*
优化过的ISA,有weak指针指向或者引用计数存储在sideTable中,
那就找到它消灭它
*/
clearDeallocating_slow();
}
assert(!sidetable_present());
}
6. 😜😜😜😜😜😜😜😜😜😜😜😜😜😜😜😜
关于weak引用的底层实现入口方法:weak_clear_no_lock
NEVER_INLINE void objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
//获取当前obj对应的SideTable
SideTable& table = SideTables()[this];
//加锁
table.lock();
//有weak指针指向
if (isa.weakly_referenced) {
//传入弱引用表和当前对象,清空
weak_clear_no_lock(&table.weak_table, (id)this);
}
//有引用计数存储在sidetable中
if (isa.has_sidetable_rc) {
//传入当前对象,擦除存储
table.refcnts.erase(this);
}
//解锁
table.unlock();
}
weak_clear_no_lock函数 的实现
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//1. 获取当前对象
objc_object *referent = (objc_object *)referent_id;
//2. 传入全局弱引用表和当前对象,取出weak_entry_t
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
//3. 以下处理目的 : 使弱引用归 0
//3.1 定义弱引用持有者数组
weak_referrer_t *referrers;
//数量
size_t count;
//3.2 找到弱引用持有者放进数组
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//3.3 循环取出弱引用持有者赋值为 nil
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
//有弱引用错误, weak持有不合法
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
//4. 移除entry
weak_entry_remove(weak_table, entry);
}
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
ASSERT(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
/*
hash_pointer:获取当前对象地址值 按位&上 弱引用表的 mask值,
最终得到索引-- >index
*/
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
//从0开始位移
size_t hash_displacement = 0;
//弱引用哈希表中的对象不等于当前对象就不停止查找,
//简单来说就是: 找到弱引用指针存储在引用表中的位置
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
//取出weak_entry_t
return &weak_table->weak_entries[index];
}
总结: 在确定有弱引用时
- 第一步:调用
weak_entry_for_referent函数,传入全局弱引用表和当前对象;获取weak_entry_t - 第二步:从
weak_entry_t的referrers属性中找到弱引用持有者放进数组 - 第三步:for循环取出弱引用持有者赋值为
nil - 第四步:调用
weak_entry_remove函数,从全局弱引用表中移除当前entry
初始化弱引用图:

__weak和__unsafe_unretained的区别是什么 ?assignn呢 ?
- 共同点:
__weak和__unsafe_unretained都是弱引用指针,都不会对所引用的对象产生强引用 - 区别是:
__weak指向的对象销毁时,会自动让指针置为nil__unsafe_unretained是不安全的 ,指向的对象销毁时,ARC并不会对它做任何处理,这时 __unsafe_unretained 中存储着一个被释放的对象的地址 - 如果这个时候刚好向__unsafe_unretained 指针指向的对象发消息,那就会造成Crash. 也就是常见的野指针(Thread 1: EXC_BAD_ACCESS(code=1,address=0x228a2fa5c578))如下图

assign: 多用来修饰基本数据类型assign关键字修饰的数据在setter方法中不会生成相关的内存管理代码assign关键字修饰的对象释放后,指针不会被清空依然指向释放前的对象;这很危险,在后续的操作中如果用到该对象会出现坏内存访问导致崩溃,所以不会用来修饰OC对象
__weak 和 weak
__weak用于修饰指针变量,weak用于属性声明OC对象- __weak 指向的对象销毁时,会自动让指针置为
nil;weak指向的对象一旦释放了,ARC会及时通过运行时从该对象的弱引用表中找到weak指针并移除。
总结
嫌上面的内容太多,那就看简化版的
runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。- weak表其实是一个hash(哈希)表,key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
稍微详细版
初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。添加引用时:objc_initWeak函数会调用-- >storeWeak() 函数,storeWeak() 的作用是更新指针指向,创建对应的弱引用表。释放时: 调用clearDeallocating函数。 clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中移除,最后清理对象的记录。
小尾巴
大家可能看了这篇文章还是晕乎乎的,主要是对里面的数据结构不是很清楚,我又找到2篇很好的文章,看完这个,保管完全不会晕乎乎了。