一、iOS 引用计数概述 对象的引用方式分为强引用和弱引用,对象的强引用和弱引用信息保存在 SideTables 中,SideTables 是全局的哈希数组,里面存储了有限数量的 SideTable,多个对象会共用一个 SideTable,也就是说每个 SideTable 中存储了多个对象的引用计数信息。 底层通过 indexForPointer 函数计算某个对象使用的是 SideTables 数组中的哪个 index 位置的 SideTable:
1 2 3 4 5 6 7 8 9 10 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif static unsigned int indexForPointer(const void *p) { uintptr_t addr = reinterpret_cast<uintptr_t>(p); return ((addr >> 4 ) ^ (addr >> 9 )) % StripeCount; }
计算结果即为对象对应的 SideTable 在 SideTables 中的 index。
根据以上逻辑,可以看到是对对象地址 addr 经过一定计算后,将结果与 StripeCount 进行模运算。所以,最终计算得到的 index 小于 StripeCount,即 SideTables 中共存储 StripeCount 个 SideTable,根据 StripeCount 定义可知,在 iPhone 和模拟器上,SideTables 存储 8 个 SideTable,Mac 上存储 64 个 SideTable。
既然 SideTable 中存储了多个对象的引用计数信息,Apple 为什么要使用多个 SideTable,而不是直接在一个 SideTable 中存储全部对象的引用计数信息?
因为 SideTable 里有一个锁,如果把所有的类的引用信息都放在同一个 SideTable,有任何一个类有改动都会对整个 SideTable 做操作,并且在操作一个类的同时,操作别的类会被锁住等待,这样会导致操作效率和查询效率都很低。而有多个 SideTable 的话,操作的都是单个 SideTable,并不会影响其他的 SideTable,这样就避免了资源竞争,提高了效率。这里引入了分离锁的概念,简而言之就是将一张表分拆成多张表,对他们分别加锁,可以实现并发操作,提升执行效率。
SideTable 的数据结构:
1 2 3 4 5 6 7 8 struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; };
需要注意的是,源码中对锁的定义使用的是 spinlock_t 类型的变量,看起来是自旋锁,实际上,Apple 已经弃用 OSSpinLock 了,最新源码是用 os_unfair_lock 来实现的,其底层执行 lock 和 unlock 的其实是 mutex_t,也就是互斥锁:
1 2 3 4 5 using spinlock_t = mutex_tt<LOCKDEBUG>; class mutex_tt : nocopy_t { os_unfair_lock mLock; }
二、强引用计数的存储 我们都知道,在非 ARC 情况下,需要开发者手动调用 retain、release 进行内存管理,并且可以调用 retainCount 去获取对象的引用计数。
开始之前先看下 isa 的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; struct { uintptr_t nonpointer : 1 ; uintptr_t has_assoc : 1 ; uintptr_t has_cxx_dtor : 1 ; uintptr_t shiftcls : 33 ; uintptr_t magic : 6 ; uintptr_t weakly_referenced : 1 ; uintptr_t deallocating : 1 ; uintptr_t has_sidetable_rc : 1 ; uintptr_t extra_rc : 19 }; };
其中,引用计数器是存储在 extra_rc 中的(存储的是引用计数减 1),但是可以看到,extra_rc 只有 19 位,可以存储的最大引用计数:2^19-1+1=524288,可能会出现不够存储的情况,即存储计数溢出。
如果出现 extra_rc 不够存储引用计数的情况,has_sidetable_rc 将会被赋值为 1,并且引用计数会存在 SideTable 的 refcnts 中。
refcnts 是一个存放着对象引用计数的散列表,key 为 objc_object,即 OC 对象,value 为引用计数,当 value 为 0 的时候,会将该记录从表中移除。所以,在 64bit 中,引用计数可以直接存储在优化过的 isa 指针中,也可能存储在 SideTable 中。
接下来看下 retainCount 这方法的实现:
1 2 3 - (NSUInteger )retainCount { return ((id )self )->rootRetainCount(); }
可以看到,其内部是调用 rootRetainCount() 方法。
在 ARC 环境下,可以调用 Core Foundation 的 CFGetRetainCount() 方法,或调用 Runtime 的 _objc_rootRetainCount(id obj) 方法来获取引用计数,它们实内部实际上也是调用 rootRetainCount() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 inline uintptr_t objc_object::rootRetainCount() { if (isTaggedPointer()) return (uintptr_t)this ; sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits); if (bits.nonpointer) { uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } sidetable_unlock(); return rc; } sidetable_unlock(); return sidetable_retainCount(); }
可以看到 rootRetainCount 主要逻辑:
如果是 Tagged Pointer,直接返回 isa 本身
如果开启了 isa 指针优化
先去 isa 中取出 extra_rc 存储的引用计数,并 + 1
再去判断 isa 中 has_sidetable_rc 是否为 1,如果为 1,就去对应 SideTable 中取出引用计数,再与上一步取到的引用计数相加返回
如果没有开启 isa 指针优化,直接去对应 SideTable 中取出引用计数返回
上面 sidetable_getExtraRC_nolock 和 sidetable_retainCount 都是去 SideTable 中取引用计数,两个函数主要逻辑基本类似,下面是 sidetable_getExtraRC_nolock 函数实现:
1 2 3 4 5 6 7 8 9 size_t objc_object::sidetable_getExtraRC_nolock() { assert(isa.nonpointer); SideTable& table = SideTables()[this ]; RefcountMap::iterator it = table.refcnts.find(this ); if (it == table.refcnts.end()) return 0 ; else return it->second >> SIDE_TABLE_RC_SHIFT; }
可以看到,强引用计数是存储在对应 SideTable 中的 refcnts 中的。
根据 retainCount 的实现也能大致反推出 retain、release 是如何修改引用计数的,这里不再补充。
三、weak 的实现原理 1、weak 引用计数的存储 (1) weak_table_t 前面提到多个对象会共用一个 SideTable,所以又会以 weak_table 作为 hash 表分散各个对象的弱应用信息,weak_table 是一个结构体:
1 2 3 4 5 6 7 8 9 10 struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; };
其中 weak_entries 是一个动态数组,其余三个元素用于 hash 表的相关操作,其中 mask = hash 数组长度 - 1,通过对象获取对应 weak_entry_t 的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 ; size_t begin = hash_pointer(referent) & weak_table->mask; size_t index = begin; 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 ; } } return &weak_table->weak_entries[index]; }
以上源码主要是计算对象对应的 weak_entry_t 在 weak_entries 数组中的 index,并根据 index 取出 weak_entry_t。hash_pointer 就是对对象 referent 的地址做个 hash,然后和 mask 做与运算,返回的结果小于 weak_table->mask ,保证了 index 不会越界,当计算结果做数组的索引。
这里 hash 冲突的解决方案是计算出 hash 位置 index,判断 index 对应的 weak_entry_t 中存储的 referent(即弱引用该“对象的指针”的指针) 是否与目标 referent 相等,不相等的话后移一位继续判断,并将 hash_displacement ++,记录移动次数。当 hash_displacement 的值大于 max_hash_displacement 时,直接返回 nil。当 index == begin 时,即遍历一圈也没找到目标对象,直接调用 bad_weak_table 报错。
(2) weak_entry_t 每个 weak_entry_t 存储着一个对象的弱引用信息。weak_entry_t 的结构与 weak_table_t 类似,也是一个 hash 表。其中存储的元素是 weak_referrer_t ,实质是弱引用对象指针的指针。通过操作指针的指针,可以实现 weak 引用的指针在对象析构后,指向 nil。
weak_entry_t 的数据结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 struct weak_entry_t { DisguisedPtr<objc_object> referent; weak_referrer_t *referrers union { struct { weak_referrer_t *referrers; uintptr_t out_of_line_ness : 2 ; uintptr_t num_refs : PTR_MINUS_2; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; bool out_of_line() { return (out_of_line_ness == REFERRERS_OUT_OF_LINE); } weak_entry_t& operator=(const weak_entry_t& other) { memcpy(this , &other, sizeof (other)); return *this ; } weak_entry_t(objc_object *newReferent, objc_object **newReferrer) : referent(newReferent) { inline_referrers[0 ] = newReferrer; for (int i = 1 ; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil ; } } };
其中 WEAK_INLINE_COUNT 定义如下:
1 #define WEAK_INLINE_COUNT 4
其中 DisguisedPtr<objc_object> referent 为弱引用对象指针摘要,可以简单理解为被弱引用的对象,只不过这里使用了摘要的形式存储(所谓摘要,其实是把地址取负)。
2、weak 实现流程 (1) 初始化 当我们初始化一个 weak 对象时,runtime 会调用 objc_initWeak 函数:
1 2 3 4 5 6 7 8 9 10 11 12 id objc_initWeak(id *location, id newObj) { if (!newObj) { *location = nil ; return nil ; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }
该方法有两个参数 location 和 newObj:
location__weak 指针的地址
newObj 所引用的对象
(2) 添加引用 objc_initWeak 函数会调用 storeWeak 函数,用于更新指针指向,创建对应的弱引用表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 template bool HaveOld, bool HaveNew, bool CrashIfDeallocating> static id storeWeak(id *location, objc_object *newObj) { Class previouslyInitializedClass = nil ; id oldObj; SideTable *oldTable; SideTable *newTable; retry: if (HaveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil ; } if (HaveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil ; } SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable); if (HaveOld && *location != oldObj) { SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable); goto retry; } if (HaveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id )newObj)); previouslyInitializedClass = cls; goto retry; } } if (HaveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } if (HaveNew) { newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id )newObj, location, CrashIfDeallocating); if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } *location = (id )newObj; } else { } SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable); return (id )newObj; }
storeWeak 函数用来更新弱引用指针的指向,创建对应的弱引用表:
storeWeak 方法实际上是接收的参数,分别是 haveOld、haveNew 和 crashIfDeallocating ,这三个参数都是以模板的方式传入的,是三个 bool 类型的参数。 分别表示 weak 指针之前是否指向了一个弱引用、weak 指针是否需要指向一个新的引用、若果被弱引用的对象正在析构,此时再弱引用该对象是否应该 crash。
该方法维护了 oldTable 和 newTable 分别表示旧的引用弱表和新的弱引用表,它们都是 SideTable 的 hash 表。
如果 weak 指针之前指向了一个弱引用,则会调用 weak_unregister_no_lock 函数将旧的 weak 指针地址移除。
如果 weak 指针需要指向一个新的引用,则会调用 weak_register_no_lock 函数将新的 weak 指针地址添加到弱引用表中。
调用 setWeaklyReferenced_nolock 方法修改 weak 新引用的对象的 bit 标志位。
这里涉及到两个关键的函数:
weak_unregister_no_lockweak ptr 地址 从 obj 的 weak_entry_t 中移除
weak_register_no_lockweak ptr 地址 注册到 obj 对应的 weak_entry_t 中
其中 weak_register_no_lock 实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; if (!referent || referent->isTaggedPointer()) return referent_id; bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL (*)(objc_object *, SEL)) object_getMethodImplementation((id )referent, SEL_allowsWeakReference); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil ; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } if (deallocating) { if (crashIfDeallocating) { _objc_fatal("Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation." , (void *)referent, object_getClassName((id )referent)); } else { return nil ; } } weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); } else { weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); } return referent_id; }
若引用计数使用了 taggedPointer,则不会做任何引用计数。
判断 referent_id 是否正在被析构以及 referent_id 是否支持 weak 引用。如果 referent_id 不能够被 weak 引用,则直接返回 nil。
如果 referent_id 能够被 weak 引用,则将 referent_id 对应的 weak_entry_t 从 weak_table 的 weak_entry_t 哈希数组中找出来,如果不存在,则新建。
调用 append_referrer 函数将 referrer 插入到 weak_entry_t 的引用数组中。
将 weak_entry_t 插入到 weak_table 中,并返回 referent_id。
referrer 插入到 weak_entry_t 中调用了函数 append_referrer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 static void append_referrer(weak_entry_t *entry, objc_object **new_referrer){ if (! entry->out_of_line()) { for (size_t i = 0 ; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil ) { entry->inline_referrers[i] = new_referrer; return ; } } weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof (weak_referrer_t)); for (size_t i = 0 ; i < WEAK_INLINE_COUNT; i++) { new_referrers[i] = entry->inline_referrers[i]; } entry->referrers = new_referrers; entry->num_refs = WEAK_INLINE_COUNT; entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; entry->mask = WEAK_INLINE_COUNT-1 ; entry->max_hash_displacement = 0 ; } assert(entry->out_of_line()); if (entry->num_refs >= TABLE_SIZE(entry) * 3 /4 ) { return grow_refs_and_insert(entry, new_referrer); } size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0 ; 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; } weak_referrer_t &ref = entry->referrers[index]; ref = new_referrer; entry->num_refs++; }
如果 weak_entry 未使用动态数组,且静态数组还有空间,直接找空位存放。
如果没有位置存放,则升级为动态数组,如果动态数组中元素个数大于或等于数组位置总空间的 3/4,则数组空间扩容当前空间大小 1 倍。
插入到 weak_entry 中。
如果 weak 指针之前指向了一个弱引用,则会调用 weak_unregister_no_lock 方法将旧的 weak 指针地址移除,weak_unregister_no_lock 实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; weak_entry_t *entry; if (!referent) return ; if ((entry = weak_entry_for_referent(weak_table, referent))) { remove_referrer(entry, referrer); bool empty = true ; if (entry->out_of_line() && entry->num_refs != 0 ) { empty = false ; } else { for (size_t i = 0 ; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i]) { empty = false ; break ; } } } if (empty) { weak_entry_remove(weak_table, entry); } }
从 weak_table 中找出 referent 对应的 weak_entry_t。
在 weak_entry_t 中移除 referrer。
判断 weak_entry_t 中是否还有元素,如果已经没有元素了,则将 weak_entry_t 从 weak_table 中移除。
(1)(2)步总结如下:
1、调用 indexForPointer 获取对象所用的 SideTable 在 SideTables 中的 index,从而取到 SideTable。
SideTables 是全局 hash 数组,在 iPhone 和模拟器上,SideTables 存储 8 个 SideTable,Mac 上存储 64 个 SideTable,SideTable 数量即 StripeCount。
计算 index 时,将对象地址经过一定位移计算后,与 StripeCount 进行模运算,结果即 index,同时确保了结果不会超过 StripeCount。
2、调用 weak_entry_for_referent 从 weak_table 中的 weak_entries 中获取对应 weak_entry_t。
weak_table 中的 weak_entries 同样是个 hash 数组,里面存储了多个 weak_entry_t,每个 weak_entry_t 存储 1 个对象的弱引用信息。
计算 weak_entry_t 在 weak_entries 数组中的 index 方式:将对象地址经过 hash_pointer 函数进行 hash,然后和 mask 进行与运算后得到 index,保证了 index 不会越界。 如果对应 index 位置已经有 weak_entry_t 了,就取出其中的 referent(即弱引用对象指针的指针) 是否与目标 referent 相等,不相等的话后移一位继续判断,并将 hash_displacement ++,记录移动次数。当 hash_displacement 的值大于 max_hash_displacement 时,直接返回 nil。 当 index == begin 时,即遍历一圈也没找到目标对象,直接调用 bad_weak_table 报错。
3、runtime 调用 objc_initWeak 初始化 weak 指针地址指向对象地址,objc_initWeak 中调用 storeWeak,相关主要逻辑在 storeWeak 中。
如果 weak 指针之前指向了一个弱引用,则会调用 weak_unregister_no_lock 函数将旧的 weak 指针地址移除。
如果 weak 指针需要指向一个新的引用,则会调用 weak_register_no_lock 函数将新的 weak 指针地址添加到弱引用表中。
weak_register_no_lock 函数中调用 append_referrer 函数将 referrer(弱引用对象指针的指针)插入到 weak_entry_t 的引用数组。
append_referrer 中会判断 weak_entry 是否是使用了动态数组及动态数组是否需要扩容(当前已使用空间大于等于空间 3/4 将进行扩容,扩容使空间翻倍)
四、对象的释放 当对象引用计数变为 0 时,其 dealloc 方法会被调用,下面是 LLVM 官方文档 对 dealloc 过程的一个描述:
A class may provide a method definition for an instance method named dealloc. This method will be called after the final release of the object but before it is deallocated or any of its instance variables are destroyed. The superclass’s implementation of dealloc will be called automatically when the method returns.
大致意思是:dealloc 方法在对象释放(最后一次 release)之后调用,但是对象的实例变量(Ivars)此时还没有释放。dealloc 方法返回时,会自动调用父类的 dealloc 方法。
上面提到,对象释放后,会调用 dealloc 方法,其内部的实例变量还未销毁,那它的实例变量何时销毁呢?文档中有相关介绍:
The instance variables for an ARC-compiled class will be destroyed at some point after control enters the dealloc method for the root class of the class. The ordering of the destruction of instance variables is unspecified, both within a single class and between subclasses and superclasses.
也就是说,ARC 环境下,实例变量在调用根类的 dealloc 方法([NSObject dealloc])后的某个时刻被销毁。但实例变量的销毁顺序是不固定的,无论是在单个类内还是在子类和父类之间。
下面是 dealloc 方法的实现:
1 2 3 - (void )dealloc { _objc_rootDealloc(self ); }
dealloc 方法内部调用了 _objc_rootDealloc 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } _objc_rootDealloc 又会调用 rootDealloc 方法: inline void objc_object::rootDealloc() { if (isTaggedPointer()) return ; if (fastpath(isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc)) { assert(!sidetable_present()); free(this ); } else { object_dispose((id )this ); } }
根据以上逻辑可以知道,接下来会调用 object_dispose 函数:
1 2 3 4 5 6 7 8 9 10 11 id object_dispose(id obj) { if (!obj) return nil ; objc_destructInstance(obj); free(obj); return nil ; }
其内部主要调用了 objc_destructInstance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void *objc_destructInstance(id obj) { if (obj) { bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); } return obj; }
清理相关引用逻辑主要是在 clearDeallocating 中实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 inline void objc_object::clearDeallocating() { if (slowpath(!isa.nonpointer)) { sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { clearDeallocating_slow(); } assert(!sidetable_present()); }
接下来看下 clearDeallocating_slow 函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this ]; table.lock(); if (isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id )this ); } if (isa.has_sidetable_rc) { table.refcnts.erase(this ); } table.unlock(); }
上面逻辑,如果对象被弱引用,调用 weak_clear_no_lock 清理 weak_table。如果对象采用了 SideTable 强引用计数,则移除相关强引用信息。
接下来看下 weak_clear_no_lock 逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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); if (entry == nil ) { return ; } 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 ; } else if (*referrer) { _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(); } } } weak_entry_remove(weak_table, entry); }
该函数主要逻辑是遍历所有弱引用该对象的 weak 指针,并将 weak 指针置为 nil,这也就是 weak 指针在对象释放的时候会被置为 nil 的原因。
以上是对象释放后,清理强弱引用相关逻辑,上面 objc_destructInstance 函数内部还有个 C++ 析构函数的调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void *objc_destructInstance(id obj) { if (obj) { bool cxx = obj->hasCxxDtor(); if (cxx) object_cxxDestruct(obj); } return obj; }
那调用的析构函数 object_cxxDestruct 是什么呢?内部有哪些逻辑呢?这里不再深究,直接给出总结:object_cxxDestruct 内部最终会调用 .cxx_destruct 的函数,《Effective Objective-C 2.0》提到过这个函数:
When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.
根据介绍,.cxx_destruct 是编译器自动生成的,ARC 环境下,会借用这个函数实现自动内存释放的工作。
其实,.cxx_destruct 是在 clang 编译期间生成的,只有当前类拥有实例变量(不论是不是用 property)时,这个函数才会自动生成,且父类的实例变量不会导致子类拥有这个函数,其内部最终会调用 emitCXXDestructMethod 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static void emitCXXDestructMethod(CodeGenFunction &CGF , ObjCImplementationDecl *impl){ CodeGenFunction::RunCleanupsScope scope(CGF ); llvm::Value *self = CGF .LoadObjCSelf(); const ObjCInterfaceDecl *iface = impl->getClassInterface(); for (const ObjCIvarDecl *ivar = iface->all_declared_ivar_begin(); ivar; ivar = ivar->getNextIvar()) { QualType type = ivar->getType(); QualType::DestructionKind dtorKind = type.isDestructedType(); if (!dtorKind) continue ; CodeGenFunction::Destroyer *destroyer = 0 ; if (dtorKind == QualType::DK_objc_strong_lifetime) { destroyer = destroyARCStrongWithStore; } else { destroyer = CGF .getDestroyer(dtorKind); } CleanupKind cleanupKind = CGF .getCleanupKind(dtorKind); CGF .EHStack.pushCleanup<DestroyIvar>(cleanupKind, self , ivar, destroyer, cleanupKind & EHCleanup); } assert(scope.requiresCleanups() && "nothing to do in .cxx_destruct?" ); }
根据上述逻辑可以发现,该函数作用是遍历当前对象所有的实例变量(Ivars),调用 objc_storeStrong 函数:
1 2 3 4 5 6 7 id objc_storeStrong(id *object, id value) { value = [value retain ]; id oldValue = *object; *object = value; [oldValue release]; return value; }
可以看到调用过 objc_storeStrong 函数后,这个实例变量就被 release 和设置成 nil 了。