一、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
了。