iOS开发者都知道,当一个对象被释放时,所有对这个对象弱引用的指针都会释放并置为nil,那么系统是如何存储这些弱引用对象的呢?又是如何在一个对象释放时,将这些指向即将释放对象的弱引用的指针置为nil的呢?下面我们通过分析SideTable
的结构来进一步了解内存管理的弱引用存储细节。
结构 在runtime中,有四个数据结构非常重要,分别是SideTables
,SideTable
,weak_table_t
和weak_entry_t
。它们和对象的引用计数,以及weak引用相关。
SideTables 下面我们看下SideTables的结构:
1 2 3 static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); }
reinterpret_cast
,是C++里的强制类型转换符,我们看下SideTableBuf
的定义。上面代码,我们看到StripedMap实际上返回的是一个SideTableBuf对象,那么我们来看下SideTableBuf对象:
1 2 3 4 5 6 7 alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof (StripedMap<SideTable>)];
SideTableBuf
是一个外部不可见的静态内存区块,存储StripedMap<SideTable>
对象。它是内存管理的基础。
我们接下来在看下StripedMap
的结构
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 enum { CacheLineSize = 64 }; template<typename T>class StripedMap {#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 };#else enum { StripeCount = 64 };#endif struct PaddedT { T value alignas(CacheLineSize); }; PaddedT array[StripeCount]; static unsigned int indexForPointer(const void *p) { uintptr_t addr = reinterpret_cast<uintptr_t>(p); return ((addr >> 4 ) ^ (addr >> 9 )) % StripeCount; } public: T& operator[] (const void *p) { return array[indexForPointer(p)].value; } const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this )[p]; } };
StripedMap
是一个以void *
为hash key, T为vaule的hash 表。StripedMap
的所有T类型数据都被封装到array中。
综上我们得出SideTables
的机构实际是下图所示:
SideTable 下面来看下sideTable
的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; SideTable() { memset(&weak_table, 0 , sizeof (weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable." ); } };
上面是我们简化后的SideTable
结构体,包含了:
保证原子属性的自旋锁spinlock_t
记录引用计数值的RefcountMap
用于存储对象弱引用的哈希表 weak_table_t
自旋锁(slock)我们这里就不做过多介绍了,我们先来看下RefcountMap,看下RefcountMap
结构
1 2 3 4 5 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true > RefcountMap;
DenseMap是llvm库中的类,是一个简单的二次探测哈希表,擅长支持小的键和值。RefcountMap
是一个hash map,其key是obj的DisguisedPtr<objc_object>
,而value,则是obj对象的引用计数
,同时,这个map还有个加强版功能,当引用计数为0时,会自动将对象数据清除。
上面我们知道了,refcnts是用来存放引用计数的,那么我们如何获取一个对象的引用计数呢?
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 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(); }
从上面的代码我们可以得出:retainCount = isa.extra_rc + sidetable_getExtraRC_nolock
,即引用计数=isa指针中存储的引用计数+sidetable中存储的引用计数
那么sidetable_getExtraRC_nolock
是如何从sideTable中获取retainCount
的呢? 下面我们来看下这个方法的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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的RefcountMap,下面我们接着看另外一个属性weak_table
weak_table 我们都知道weak_table是对象弱引用map,它记录了所有弱引用对象的集合。
我们先来看下weak_table_t
的定义:
1 2 3 4 5 6 7 8 9 10 11 12 struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; };
weak_entries
实质上是一个hash数组,数组中存储weak_entry_t
类型的元素。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 40 41 42 #define WEAK_INLINE_COUNT 4 #define REFERRERS_OUT_OF_LINE 2 struct weak_entry_t { DisguisedPtr<objc_object> referent; 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]; }; }; }
从上面的介绍我们可以总结SideTables
和SideTable
以及weak_table_t
在层级上的关系图如下:
上图是从数据结构的角度来看弱引用的保存,下面我们来看下从垂直方向来看
从上面的总结中我们可以看到,弱引用的存储实际上一个三级的哈希表,通过一层层的索引找到或者存储对应的弱引用。那当向weak_table_t
中插入或查找某个元素时是如何操作的呢?算法是什么样的呢?
weak_entry_for_referent 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 / 在weak_table中查找所有弱引用referent的对象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_table->weak_entries
中的时候发现判断是否遍历完一遍的时候使用的方法
1 index = (index+1 ) & weak_table->mask;
假设当前数组长度8,下标分别是0-7,上面weak_table->mask
= 7 = 0111。
下标
计算后结果
index = 0
index & mask = 0000 & 0111 = 0000 = 0
index = 1
index & mask = 0001 & 0111 = 0001 = 1
index = 2
index & mask = 0010 & 0111 = 0010 = 2
index = 3
index & mask = 0011 & 0111 = 0011 = 3
index = 4
index & mask = 0100 & 0111 = 0100 = 4
index = 5
index & mask = 0101 & 0111 = 0101 = 5
index = 6
index & mask = 0110 & 0111 = 0110 = 6
index = 7
index & mask = 0111 & 0111 = 0111 = 7
index = 8
index & mask = 1000 & 0111 = 0000 = 0
看完上面的计算相信大家都明白了这么做的真是意图了:
可以理解为:数组遍历完成,已经和数组中所有的元素做了对比。
随着某个对象被越来越多的对象弱引用,那么这个存放弱引用该对象的所有对象的数组也会越来越大。
hash表自动扩容 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 static void weak_resize(weak_table_t *weak_table, size_t new_size) { size_t old_size = TABLE_SIZE(weak_table); weak_entry_t *old_entries = weak_table->weak_entries; weak_entry_t *new_entries = (weak_entry_t *) calloc(new_size, sizeof (weak_entry_t)); weak_table->mask = new_size - 1 ; weak_table->weak_entries = new_entries; weak_table->max_hash_displacement = 0 ; weak_table->num_entries = 0 ; if (old_entries) { weak_entry_t *entry; weak_entry_t *end = old_entries + old_size; for (entry = old_entries; entry < end; entry++) { if (entry->referent) { weak_entry_insert(weak_table, entry); } } free(old_entries); } }
从上面的代码中我们可以看到,哈希表的扩容主要分为下面几个步骤:
创建一个局部变量保存当前哈希表中保存的所有弱引用实体
新建一个容量是旧哈希表大小2倍的哈希表,同时重置num_entries
、max_hash_displacement
、weak_entries
、mask
遍历之前保存的旧的数据 将数据按照顺序依次重新插入的新建的哈希表中
释放旧数据
我们看到将旧数据插入新数据的主要方法是weak_entry_insert
,下面我们来仔细介绍下它:
weak_entry_insert 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 static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) { weak_entry_t *weak_entries = weak_table->weak_entries; assert(weak_entries != nil ); size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); size_t index = begin; size_t hash_displacement = 0 ; while (weak_entries[index].referent != nil ) { index = (index+1 ) & weak_table->mask; if (index == begin) bad_weak_table(weak_entries); hash_displacement++; } weak_entries[index] = *new_entry; weak_table->num_entries++; if (hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; } }
插入操作也很简单,主要分为下面几个步骤:
取出哈希表中所有弱引用对象的数据
遍历第一步取出的所有数据,找到第一个空位置
将要插入的实体插入到这个位置,同时更新当前weak_table
中弱引用实体个数
重置weak_table
中最大哈希冲突次数的值
插入的主要逻辑实际上并不复杂,但是我们发现最后一步
1 2 3 4 5 if (hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; }
通过上面的代码我们发现,假设weak_table
的weak_entries
最大容量为8,当前存放了3个被弱引用的对象且分别存放在下标为[0,1,2]中,同时要插入的对象new_entry
不再weak_entries
中,那么经过while循环,hash_displacement = 3
。实际上如果在没有哈希冲突的情况下我们通过hash_pointer
得到的index就应该是用来存放new_entry
的,但是因为存在哈希冲突,所以后移了3位后才找到合适的位置来存放new_entry
,因此hash_displacement
也被理解为,本应存放的位置距离实际存放位置的差值。
综上,我们分析了哈希表中获取所有弱引用某个对象的对象数组,哈希表扩容方法,以及如何在哈希表中插入一个弱引用对象。
下面我们来看下新增和释放弱引用对象的方法
objc_initWeak 1 2 3 4 5 6 7 8 9 10 11 12 13 14 id objc_initWeak(id *location, id newObj) { if (!newObj) { *location = nil ; return nil ; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }
从上面我们看出objc_initWeak
实际上是调用了storeWeak
方法,且方法调用我们可以翻译为
1 2 storeWeak<false , true , true > (location, (objc_object*)newObj)
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 enum CrashIfDeallocating { DontCrashIfDeallocating = false , DoCrashIfDeallocating = true }; template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating>static id storeWeak(id *location, objc_object *newObj) { assert(haveOld || haveNew); if (!haveNew) assert(newObj == nil ); 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::lockTwo<haveOld, haveNew>(oldTable, newTable); if (haveOld && *location != oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, 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::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id )newObj; }
storeWeak方法有点长,这也是weak引用的核心实现部分。其实核心也就实现了两个功能:
将weak指针的地址location
存入到obj对应的weak_entry_t的数组(链表)中,用于在obj析构时,通过该数组(链表)找到所有其weak指针引用,并将指针指向的地址(location
)置为nil。
如果启用了isa优化,则将obj的isa_t的weakly_referenced
位置1。置位1的作用主要是为了标记obj被weak引用了,当dealloc时,runtime会根据weakly_referenced
标志位来判断是否需要查找obj对应的weak_entry_t,并将引用置为nil。
上面的方法中,我们看到插入新值的方法为weak_register_no_lock
,清除旧值的方法为weak_unregister_no_lock
,下面我们来看下这两个方法:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 / 添加对某个对象的新的弱引用指针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; }
上面方法主要功能是:添加对某个对象的新的弱引用指针
过滤掉isTaggedPointer
和弱引用对象正在被释放这两种情况后(这里需要判断是否有自定义的释放方法),然后根据crashIfDeallocating
参数确定是崩溃还是返回nil
如果对象没有正在被释放,那么从weak_table
中取出指向referent
的弱引用指针实体,如果weak_table
中存在指向referent
的指针数组那么在这个数组中添加要新增的指针
如果weak_table
没有找到指向referent
的弱指针数组,那么新建一个weak_entry_t
对象,将这个对象拆入到weak_table
中(需要判断weak_table是否需要扩容)
下面我们来看下具体的插入方法:
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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++; }
插入的过程主要分下面三种情况:
如果inline_referrers
没有存储满,直接存储到inline_referrers
中
如果inline_referrers
个数是4个了,在插入,就需要将inline_referrers
拷贝到referrers
,然后进入第三步。
如果inline_referrers
存储满了,判断是否需要扩容,然后将数据存储到referrers
中。
下面我们来看下扩容的方法:
grow_refs_and_insert 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 __attribute__((noinline, used))static void grow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer) { assert(entry->out_of_line()); size_t old_size = TABLE_SIZE(entry); 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)); entry->num_refs = 0 ; entry->max_hash_displacement = 0 ; for (size_t i = 0 ; i < old_size && num_refs > 0 ; i++) { if (old_refs[i] != nil ) { append_referrer(entry, old_refs[i]); num_refs--; } } append_referrer(entry, new_referrer); if (old_refs) free(old_refs); }
看完了新增弱引用指针的操作,接下来我们看下如何删除弱引用指针即weak_unregister_no_lock
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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_unregister_no_lock
的实现逻辑比较简单,其实主要的操作为:
首先,它会在weak_table
中找出referent
对应的weak_entry_t
在weak_entry_t
中移除referrer
移除元素后,判断此时weak_entry_t
中是否还有元素 (empty==true?)
如果此时weak_entry_t
已经没有元素了,则需要将weak_entry_t从weak_table
中移除
而对于remove_referrer
方法,我们来简单的看下他的实现:
remove_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 55 static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) { if (! entry->out_of_line()) { for (size_t i = 0 ; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == old_referrer) { entry->inline_referrers[i] = nil ; return ; } } _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 ; } 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++; 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 ; } } entry->referrers[index] = nil ; entry->num_refs--; }
上面的描述也很简单,大概的流程为:
在entry->inline_referrers
中一次查找值为old_referrer
的指针 如果找到就清空如果没找到报错
在entry->referrers
中查找值为old_referrer
的指针,如果找到则置空同时entry->num_refs
做-1操作(使用inline_referrers
存储时不会更新num_refs
值因此移除也不用-1)
我们在删除指向某个对象的某个弱引用指针之后,还会对存储指向该对象的弱引用指针数组做判空操作,如果发现数组为空,那表示目前没有弱引用指针指向这个对象,那我们需要将这个对象从weak_table
中移除。下面我们来看下移除方法weak_entry_remove
。
weak_entry_remove 1 2 3 4 5 6 7 8 9 10 11 12 13 static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { if (entry->out_of_line()) free(entry->referrers); bzero(entry, sizeof (*entry)); weak_table->num_entries--; weak_compact_maybe(weak_table); }
上面方法的主要操作为:
将没有弱引用的对象从全局的weak_table
中移除
减少weak_table
中存储的弱引用对象个数
判断weak_table
是否需要缩小容量
上面的所有就是当我们将一个obj作weak引用时,所发生的事情。那么,当obj释放时,所有weak引用它的指针又是如何自动设置为nil的呢?接下来我们来看一下obj释放时,所发生的事情。
Dealloc 当对象引用计数为0时,runtime会调用_objc_rootDealloc方法来析构对象,实现如下:
1 2 3 4 5 6 7 8 9 10 11 - (void )dealloc { _objc_rootDealloc(self ); }void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); }
_objc_rootDealloc
又会调用objc_object的rootDealloc
方法
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 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 ); } }
因此根据上面代码判断,如果obj被weak引用了,应该进入object_dispose((id)this)
分支,下面我们来看下object_dispose
方法:
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 ; }
析构obj主要是看objc_destructInstance
方法,下面我们来看下这个方法的实现
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
中实现的,下面我们再来看下这个方法:
clearDeallocating 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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()); }
这里的清理方法有两个分别为sidetable_clearDeallocating
和clearDeallocating_slow
,
我们先来看下clearDeallocating_slow
:
clearDeallocating_slow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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
的清理工作,同时将所有weak引用该对象的ptr置为nil。
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); }
上面就是为什么当对象析构时,所有弱引用该对象的指针都会被设置为nil的原因。
总结 综上我们讲述了SideTable的结构,以及如何使用SideTable存储和清除对象和指向这些对象的指针地址。从而在侧面验证了弱引用的存储方式以及在对象释放时如何将弱引用的指针置空。读完这篇文章相信你对于SideTable结构和弱引用已经有了一个比较全面的认识。