preface
The previous article covered reference counting tables in hash tables, but left out one weak reference table. What does the reference count table do when weak is used for weak references? This article will analyze the weak reference table from __weak.
Weak principle
-
Let’s start with the following code:
NSObject *obj = [NSObject alloc]; NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(obj)), obj); __weak typeof(id) wushuang = obj; NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(obj)), obj); NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang); NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang); NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(obj)), obj); Copy the code
The print result is as follows:
You might wonder, what does a weak reference actually do? Why is the reference count of the weak pointer 2? Wushuang and obj are both Pointers. Why is the reference count of obj unchanged? With that in mind, let’s explore the objC4-818.2 source code
objc_initWeak
-
Looking at the assembly, you can see that adding the __weak modifier leads to the objc_initWeak function, a process that is determined by LLVM.
id objc_initWeak(id *location, id newObj) // Location weak pointer address, newObj object { if(! newObj) { *location = nil;return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } Copy the code
In this code, location is the weak pointer, newObj is the NSObject object, and will eventually go to storeWeak. Observe that objc_destroyWeak will also go to storeWeak, but with different parameters
-
The main code of storeWeak is as follows
enum CrashIfDeallocating { DontCrashIfDeallocating = false, DoCrashIfDeallocating = true }; 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) { ... } else{... }if (haveNew) { newTable = &SideTables()[newObj]; // Get the hash table of the object } else{... } SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);// Lock the hash table if(haveOld && *location ! = oldObj) { ... }// Deadlocks between the weak reference mechanism and the + initialization mechanism can be prevented by ensuring that no weak reference object has an uninitialized ISA if (haveNew && newObj) { ... } // Call the method when it is destroyed 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 ? CrashIfDeallocating : ReturnNilIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if(! newObj->isTaggedPointerOrNil()) {... }// Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); / / unlock callSetWeaklyReferenced((id)newObj); return (id)newObj; } Copy the code
According to the code analysis, the weak_register_NO_lock function is eventually entered
-
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; // Referent: newObject, NSObject object objc_object **referrer = (objc_object **)referrer_id; // Location pointer, weak pointer if (referent->isTaggedPointerOrNil()) return referent_id; // ensure that the referenced object is viable if (deallocatingOptions == ReturnNilIfDeallocating || deallocatingOptions == CrashIfDeallocating) { bool deallocating; if(! referent->ISA() - >hasCustomRR()) { deallocating = referent->rootIsDeallocating(a);/ / returns false } else{... }if(deallocating) { ... }}// now remember it and where it is being stored weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { // Get the host object's weak_entry_t based on the object append_referrer(entry, referrer); // Insert if present } else { // Create weak_entry_t if it does not exist weak_entry_t new_entry(referent, referrer); / / create weak_entry_t weak_grow_maybe(weak_table); // Check the size of weak_table to determine whether expansion is required weak_entry_insert(weak_table, &new_entry); / / insert } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; } Copy the code
- The core of the method is the basis
referent
Object to findweak_table
In theweak_entries
A linked listweak_entry_t
objectentry
:- if
entry
Exists, willWeak pointer
depositweak_entry_t
In thereferrers
In a linked list, store whenFollow the 3/4 double expansion rule. - if
entry
Does not exist, according toobject
andWeak pointer
Create a new oneentry
And thenentry
insertweak_table
theweak_entries
Chain in the table.
- if
- The core of the method is the basis
According to the analysis, __weak does not cause the change of the reference count, so that means the reason for the change of the reference count of the weak object is related to the call of the weak object when printing. Through the breakpoint assembly analysis, the code will go to the objc_loadWeak method when printing
objc_loadWeak
-
The code is as follows:
id objc_loadWeak(id *location) { if(! *location)return nil; return objc_autorelease(objc_loadWeakRetained(location)); } Copy the code
The code is mainly the result returned by objc_loadWeakRetained call, and then call the objc_autorelease method. The core code is positioned to objc_loadWeakRetained
-
Objc_loadWeakRetained:
id objc_loadWeakRetained(id *location) { id obj; id result; Class cls; SideTable *table; retry: // fixme std::atomic this load obj = *location; // Get the object object according to the weak pointer if (obj->isTaggedPointerOrNil()) return obj; table = &SideTables()[obj]; // Get the hash table based on the object table->lock(a);/ / lock if(*location ! = obj) { ... } result = obj; cls = obj->ISA(a);if (! cls->hasCustomRR()) { ASSERT(cls->isInitialized()); if (! obj->rootTryRetain()) { // obj reference count changesresult = nil; }}else{... } table->unlock(a);return result; } Copy the code
- Based on the breakpoint walk, the final code is in
rootTryRetain
- Based on the breakpoint walk, the final code is in
-
RootTryRetain:
ALWAYS_INLINE bool objc_object::rootTryRetain(a) { return rootRetain(true, RRVariant::Fast) ? true : false; } Copy the code
We ended up with the familiar rootRetain code, where the reference count was +1.
-
When the weak object is used, the reference count is +1 for a short time. However, after the weak object is used, the objc_release method is called to perform the reference count -1. Therefore, when WusHuang is printed for many times, the reference count is always 2
objc_destroyWeak
-
When weak objects are destroyed, objc_destroyWeak will be called, and finally the code will enter storeWeak. According to the above analysis, the core code of destruction is to call Weak_unregister_NO_lock, and the core code is as follows:
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) { objc_object *referent = (objc_object *)referent_id; / / object objc_object **referrer = (objc_object **)referrer_id; / / weak pointer weak_entry_t *entry; if(! referent)return; if ((entry = weak_entry_for_referent(weak_table, referent))) { remove_referrer(entry, referrer); // remove referrer // remove weak reference pointer 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); / / remove the entry}}}Copy the code
This part of the code is easy to understand, which is to obtain the entry based on the object and weak reference table, then delete the weak pointer, and finally remove the entry
Weak reference table structure diagram
-
The structure diagram of the weak reference table is as follows: