提及 weak 引用,大多数人都知道在什么时候要用它,如果不知道的话:ARC内存管理以及循环引用,其实对于手动管理堆内存来说,比如 C 语言,并不存在所谓的强引用和弱引用,ARC 这种自动引用计数管理内存的方式,导致了两个对象循环引用,从而产生内存泄漏。循环引用就像是双向链表的两个结点的 next 指针互相指向,当我们用 C 语言实现循环链表的时候,即使没有 weak,也能很好的管理每个结点的内存。因此,weak 是引用计数管理内存的产物,它需要提供这种机制来避免循环引用造成的内存泄漏。
/** * The internal structure stored in the weak references table. * It maintains and stores * a hash set of weak references pointing to an object. * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set * is instead a small inline array. */ structweak_entry_t { objc_object* referent; union { struct { // typedef id weak_referrer_t weak_referrer_t *referrers; // 保存弱引用地址的 hash set uintptr_t out_of_line_ness : 2; // 标记是否需要用 set 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]; // 轻量级内联数组 }; }; }
/// Adds an (object, weak pointer) pair to the weak table. /// 向 weak table 中添加一个 <object、weak pointer> 的键值对 /// 如果 object 已经存在,则向 weak_entry 中插入 *referrer,否则创建一个 weak_entry,将 *referrer 插入 /// 当哈希表的装填因子满足一定条件时,扩大哈希表长度,重新哈希。 id weak_register_no_lock(weak_table_t *weak_table, id referent, id *referrer, bool crashIfDeallocating);
/// Removes an (object, weak pointer) pair from the weak table. /// 当键值对存在时,从 weak_entry 中删除一条数据,如果 weak_entry 为空,则从 weak_table 中删除键值对 voidweak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
#if DEBUG /// Returns true if an object is weakly referenced somewhere. /// 如果 weak_table 中有关于这个对象的数据,返回 true boolweak_is_registered_no_lock(weak_table_t *weak_table, id referent); #endif
/// Called on object destruction. Sets all remaining weak pointers to nil. /// 在对象的析构方法中调用,设置所有的弱引用指针为 nil voidweak_clear_no_lock(weak_table_t *weak_table, id referent);
/** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * 在 dealloc 函数中调用,将所有该对象的弱引用设置为 nil,所有它们不应该再被使用。 * * @param weak_table * @param referent The object being deallocated. */ void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { // 将 id 类型转换为 objc 类型 objc_object *referent = (objc_object *)referent_id; // 通过 object 找到 table 中存的值 weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); // assert(entry) if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; }
// zero out references weak_referrer_t *referrers; size_t count; // 判断是否需要 set if (entry->out_of_line()) { // 如果是,将 referrers 指向 set referrers = entry->referrers; count = TABLE_SIZE(entry); } else { // 如果不是,将 referrers 指向内联数组 referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } // 迭代集合,依次将集合内的指针设置为 nil for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil; } elseif (*referrer) { // 如果 *referrer != nil && *referrer != referent,断点并报错 _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(); } } } // 最后将 entry 从 weak_table 中删除 weak_entry_remove(weak_table, entry); }
/** * Add the given referrer to set of weak pointers in this entry. * Does not perform duplicate checking (b/c weak pointers are never * added to a set twice). * * 将给定的 referrer 添加进 weak_entry 中的 set * * @param entry The entry holding the set of weak pointers. * @param new_referrer The new weak pointer to be added. */ staticvoidappend_referrer(weak_entry_t *entry, objc_object **new_referrer) { // 是否不需要 set if (! entry->out_of_line()) { // Try to insert inline. // 将 new_referrer 添加进内联数组的尾部 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return; } }
// Couldn't insert inline. Allocate out of line. // 创建 set weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); // This constructed table is invalid, but grow_refs_and_insert // will fix it and rehash it. // 将内联数组的内容拷贝到新创建的 set for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { new_referrers[i] = entry->inline_referrers[i]; } // 配置 entry 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()); // 当 set 的装填因子大于 3/4 时,扩展 size 并重新 hash 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; } // 由哈希码得到 index,将数据插入到该 index 处 weak_referrer_t &ref = entry->referrers[index]; ref = new_referrer; entry->num_refs++; }