Objective-C的对象采用引用计数来管理内存,如果对象被强持有,这个对象的引用计数会增加,如果对象被弱持有,这个对象的引用计数不会增加。弱持有也就是weak如何实现的呢?首先看下weak相关底层实现用到的数据结构,主要分为SideTable,weak_table_t和weak_entry_t这几个数据结构。
struct SideTable {spinlock_t slock; //锁RefcountMap refcnts; //引用计数表weak_table_t weak_table; //weak引用表
};
SideTable是Objective-C中的引用计数表,它持有了weak引用表,也就是weak_table_t类型的weak_table。
struct weak_table_t {weak_entry_t *weak_entries;size_t num_entries; //弱引用项数量uintptr_t mask; //用于计算哈希的maskuintptr_t max_hash_displacement; //允许的哈希未命中的次数
};
在weak_table_t中,持有了weak_entry_t指针,这个指针指向一个数组,数组中每个weak_entry_t代表着一个弱引用项,这个数组模拟实现了hash。
struct weak_entry_t {DisguisedPtr<objc_object> referent;union {struct {weak_referrer_t *referrers; //weak指针数组uintptr_t out_of_line_ness : 2;uintptr_t num_refs : PTR_MINUS_2;uintptr_t mask;uintptr_t max_hash_displacement;};struct {// out_of_line_ness field is low bits of inline_referrers[1]weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];};};
};
一个弱引用项持有了referent,也就是被引用的对象,接下来使用联合体union持有了weak_referrer_t类型的数组,当weak指针指向的数组不超过4个元素时,使用定长的数组,超过4个元素的时候,使用变长数组。
这里weak_entry_t并没有直接持有被引用的对象,而是持有了DisguisedPtr类型的对象。
template <typename T>
class DisguisedPtr {uintptr_t value;static uintptr_t disguise(T* ptr) {return -(uintptr_t)ptr;}static T* undisguise(uintptr_t val) {return (T*)-val;}public:DisguisedPtr() { }DisguisedPtr(T* ptr) : value(disguise(ptr)) { }DisguisedPtr(const DisguisedPtr<T>& ptr) : value(ptr.value) { }
};
DisguisedPtr的核心方法是disguise和undisguise,disguise所做的事情是把指针的值加了负号,undisguise再次添加负号得到原始数据。做这一层包装替换的原因是防止leaks工具把对象检测为内存泄露。
整体的数据结构大致就是这样,SideTable持有weak_table_t类型的指针,weak_table_t持有
weak_entry_t类型的指针。weak_table_t代表的是weak表,里面存放很多weak指针和weak指针指向的对象,weak_entry_t代表的weak引用项,它持有了DisguisedPtr处理过的对象的指针和weak指针。weak_entry_t在底层是使用数组存储起来的,为了加快访问查找速度,在数组的数据结构上,模拟实现了hash表。
了解了weak引用相关的数据结构之后,可以看下weak实现的过程。当我们使用weak指针,使weak指针指向一个对象后,会执行到objc_storeWeak方法。
id objc_storeWeak(id *location, id newObj)
{return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object *)newObj);
}
template <HaveOld haveOld, HaveNew haveNew,enum 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还没有初始化,先进行初始化class_initialize(cls, (id)newObj);previouslyInitializedClass = cls;goto retry;}}// 如果weak指针有旧值,先清理旧值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 ? CrashIfDeallocating : ReturnNilIfDeallocating);// 设置weak引用标记位if (!_objc_isTaggedPointerOrNil(newObj)) {newObj->setWeaklyReferenced_nolock();}//把新值复制给*location,weak指针指向newObj*location = (id)newObj;}else {// No new value. The storage is not changed.}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);callSetWeaklyReferenced((id)newObj);return (id)newObj;
}
objc_storeWeak函数调用了storeWeak函数,storeWeak函数中的核心方法是weak_register_no_lock。
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{objc_object *referent = (objc_object *)referent_id;objc_object **referrer = (objc_object **)referrer_id;//如果是taggedPointer,直接返回referent_idif (_objc_isTaggedPointerOrNil(referent)) return referent_id;// 查看引用的对象是否在释放,如果在释放,返回nil或者crashif (deallocatingOptions == ReturnNilIfDeallocating ||deallocatingOptions == CrashIfDeallocating) {bool deallocating;if (!referent->ISA()->hasCustomRR()) {deallocating = referent->rootIsDeallocating();}else {// Use lookUpImpOrForward so we can avoid the assert in// class_getInstanceMethod, since we intentionally make this// callout with the lock held.auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),referent->getIsa());if ((IMP)allowsWeakReference == _objc_msgForward) {return nil;}deallocating =! (*allowsWeakReference)(referent, @selector(allowsWeakReference));}if (deallocating) {if (deallocatingOptions == 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,并且追加weak指针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);}// Do not set *referrer. objc_storeWeak() requires that the // value not change.return referent_id;
}
weak_register_no_lock函数的核心在于通过weak_entry_for_referent查找弱引用项,如果已经有了对应的weak_entry_t,直接插入weak指针到数据里面。如果没有则新建new_entry对象,并且加入到weak_entry_t指针指向的数组里面。
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_for_referent的实现比较简单,从weak_table->weak_entries得到weak_entry_t指针,通过
hash_pointer(referent) & weak_table->mask得到哈希的索引,如果有哈希冲突,可以增加索引值,继续查找。可以看出weak_entries实际上是数组的数据结构,为了加快查找速度,在数组上实现了hash。
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{ if (! entry->out_of_line()) {//weak指针数量不超过4个for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {if (entry->inline_referrers[i] == nil) {entry->inline_referrers[i] = new_referrer;return;}}//weak指针数量超过4个,开辟内存存储weak指针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;}//weak指针数量超过4个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;//找到一个为nil的位置,然后插入new_referrerwhile (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++;
}
append_referrer函数用于处理weak_entry_t内部的weak指针,这里也同样使用了数组模拟hash,构建了hash表存储weak指针。如果一个对象的weak指针数量不超过4个, 直接使用定长数组插入。如果超过4个,找到为nil的位置并且传入new_referrer。
以上内容是weak指针添加的过程,还有一个weak指针移除的方法。
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))) {//移除referrer指针remove_referrer(entry, referrer);//如果没有weak指针指向这个对象,需要移除weak_entry_tbool 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指针,它首先查找对象对应的weak_entry_t,然后找到weak指针哈希表,移除这个weak指针,移除后要判断对象的weak指针是不是已经被清空了,如果被清空了,对应的weak_entry_t需要移除。