// StripedMap<T> is a map of void* -> T, sized appropriately // for cache-friendly lock striping. // For example, this may be used as StripedMap<spinlock_t> // or as StripedMap<SomeStruct> where SomeStruct stores a spin lock. template<typename T> classStripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif
structPaddedT { T value alignas(CacheLineSize); };
// RefcountMap disguises its pointers because we // don't want the table to act as a root for `leaks`. typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
// DisguisedPtr<T> acts like pointer type T*, except the // stored value is disguised to hide it from tools like `leaks`. // nil is disguised as itself so zero-filled memory works as expected, // which means 0x80..00 is also disguised as itself but we don't care. // Note that weak_entry_t knows about this encoding. template <typename T> classDisguisedPtr { uintptr_t value;
// The address of a __weak variable. // These pointers are stored disguised so memory analysis tools // don't see lots of interior pointers from the weak table into objects. typedef DisguisedPtr<objc_object *> weak_referrer_t;
/** * 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. */ #define WEAK_INLINE_COUNT 4
// out_of_line_ness field overlaps with the low two bits of inline_referrers[1]. // inline_referrers[1] is a DisguisedPtr of a pointer-aligned address. // The low two bits of a pointer-aligned DisguisedPtr will always be 0b00 // (disguised nil or 0x80..00) or 0b11 (any other address). // Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state. #define REFERRERS_OUT_OF_LINE 2
structweak_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 { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; //普通数组,可以存储4个元素,弱引用<=4时保存在这里。起到优化作用。 }; };
如果弱引用个数>4,则使用动态数组weak_referrer_t *referrers。referrers其实是一个哈希表,存储在里面的弱引用指针变量的地址并不是按顺序依次排列的,而是将弱引用指针变量的地址映射为一个 index,然后存储在 index 上,这样做的好处就是可以快速查找出它在referrers中的位置,否则只能遍历整个数组。这里就是源码为了提高查找效率做的优化。
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;
// Acquire locks for old and new values. // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; //从SideTablesMap哈希表中获取旧对象的SideTable } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; //从SideTablesMap哈希表中获取新对象的SideTable } else { newTable = nil; }
// Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls;
goto retry; } }
// Clean up old value, if any. if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); }
// Assign new value, if any. if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table. if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); }
// Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
/** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table. * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */ 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;
// ensure that the referenced object is viable bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, @selector(allowsWeakReference)); if ((IMP)allowsWeakReference == _objc_msgForward) { returnnil; } deallocating = ! (*allowsWeakReference)(referent, @selector(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 { returnnil; } }
// now remember it and where it is being stored 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.
- (void)dealloc { id __weak obj = self; //实际代码当然不会这么明显,比较隐蔽的一种情况是懒加载的view设置delegate为self }
崩溃:
1
objc[70479]: Cannot form weak reference to instance (0x600001778540) of class ARCPerson. It is possible that this object was over-released, or is in the process of deallocation.
/** * Return the weak reference table entry for the given referent. * If there is no entry for referent, return NULL. * Performs a lookup. * * @param weak_table * @param referent The object. Must not be nil. * * @return The table of weak referrers to this object. */ static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) { ASSERT(referent);
hash冲突解决,如果 index 处已经有元素但并不等于当前对象,表明存在 hash冲突,则和 index 的下一个位置进行比较。期间如果又找到开始的位置则调用bad_weak_table函数抛出异常。如果超过了最大哈希冲突max_hash_displacement则停止查找也返回 nil,表明哈希表冲突太多了需要扩容了。
// Grow the given zone's table of weak references if it is full. staticvoidweak_grow_maybe(weak_table_t *weak_table) { size_t old_size = TABLE_SIZE(weak_table);
// Grow if at least 3/4 full. if (weak_table->num_entries >= old_size * 3 / 4) { weak_resize(weak_table, old_size ? old_size*2 : 64); //old_size为0时,则默认设置为64个槽。 } }
/** * 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). * * @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) { if (! entry->out_of_line()) { // Try to insert inline. 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. 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. 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++; }
/** * Grow the entry's hash table of referrers. Rehashes each * of the referrers. * * @param entry Weak pointer hash set for a particular object. */ __attribute__((noinline, used)) staticvoidgrow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer) { ASSERT(entry->out_of_line());
/** * Remove old_referrer from set of referrers, if it's present. * Does not remove duplicates, because duplicates should not exist. * * @todo this is slow if old_referrer is not present. Is this ever the case? * * @param entry The entry holding the referrers. * @param old_referrer The referrer to remove. */ staticvoidremove_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--; }
/** * Remove entry from the zone's table of weak references. */ staticvoidweak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { // remove entry if (entry->out_of_line()) free(entry->referrers); bzero(entry, sizeof(*entry));
weak_table->num_entries--;
weak_compact_maybe(weak_table); }
// Shrink the table if it is mostly empty. staticvoidweak_compact_maybe(weak_table_t *weak_table) { size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full. if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) { weak_resize(weak_table, old_size / 8); // leaves new table no more than 1/2 full } }
/*********************************************************************** * object_dispose * fixme * Locking: none **********************************************************************/ id object_dispose(id obj) { if (!obj) return nil;
objc_destructInstance(obj); free(obj);
return nil; }
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. **********************************************************************/ void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects();
// This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); }
return obj; }
inlinevoid objc_object::clearDeallocating() { if (slowpath(!isa.nonpointer)) { // Slow path for raw pointer isa. sidetable_clearDeallocating(); //这里面也是清除weak,清除引用计数 } elseif (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // Slow path for non-pointer isa with weak refs and/or side table data. clearDeallocating_slow(); }
// Slow path of clearDeallocating() // for objects with nonpointer isa // that were ever weakly referenced // or whose retain count ever overflowed to the side table. 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(); }
/** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table * @param referent The object being deallocated. */ 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) { /// 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; 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; //弱引用指针的值被置位nil. } elseif (*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); }
/** * Destroys the relationship between a weak pointer * and the object it is referencing in the internal weak * table. If the weak pointer is not referencing anything, * there is no need to edit the weak table. * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location The weak pointer address. */ void objc_destroyWeak(id *location) { (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil); }