Weak is a weak reference. The counter of the referenced object is not incremented by one and is automatically set to nil when the referenced object is released. So what’s the principle of weak? The weak table is actually a hash table (dictionary is also a hash table). The Key is the address of the object and the Value is the address set of the weak pointer. Usually used to solve circular reference problems. Weak works.
Weak implementation principle summary
The Runtime maintains a weak table that stores all the weak Pointers to an object. The weak table is actually a hash table. The Key is the address of the object and the Value is the address of the weak pointer. If the number of weak Pointers is less than or equal to 4, it is an array.
The realization principle of weak can be summarized in the following three steps:
1. Initialization: The Runtime calls the objc_initWeak function and initializes a new weak pointer to the address of the object. 2. When adding a reference: the objc_initWeak function will call objc_storeWeak(), which updates the pointer pointer and creates the corresponding weak reference table. 3. When releasing, call the clearDeallocating function. The clearDeallocating function first fetches an array of weak pointer addresses based on the object’s address, then iterates through the array to set it to nil. Finally, it deletes the entry from the Weak table, cleaning up the object’s record.
Here we begin to detail each step:
1. Initialization: The Runtime calls the objc_initWeak function, which initializes a new weak pointer to the address of the object.
Sample code:
{
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
Copy the code
When we initialize a weak variable, the Runtime calls the objc_initWeak function in nsobject. mm. This function is declared in Clang as follows:
Id objc_initWeak(id *object/*weak pointer obj1*/, id value/* original object obj*/);Copy the code
The implementation of the objc_initWeak() method
// location pointer obj1, newObj original object obj id objc_initWeak(id *location, id newObj) { newObj) { *location = nil; return nil; Return storeWeak<false/*old*/, true/*new*/, true/*crash*/> (location, (objc_object*)newObj); }Copy the code
As you can see, this function is just a call entry to a deeper function. In general, entry functions make some simple judgments (such as the cache judgment in objc_msgSend), which determines whether the class object to which the pointer points is valid. Otherwise, object is registered as an __weak object pointing to value. The objc_storeWeak function should do this.
Note:
The objc_initWeak function has one prerequisite: Object must be a valid pointer that is not registered as an __weak object. Value can either be null or point to a valid object.
2. When adding a reference: the objc_initWeak function will call objc_storeWeak(), which updates the pointer pointer and creates the corresponding weak reference table.
The objc_storeWeak function is declared as follows:
The objc_storeWeak function registers the memory address of the assignment object (value) as the key value, and the memory address of the __weak variable (location) as the value, registering the weak table. id objc_storeWeak(id *location, id value);Copy the code
Objc_storeWeak () is implemented as follows:
// HaveOld: true - variable has value // false - need to be cleaned up in time, current value may be nil // HaveNew: True - New value to be assigned, the current value might be nil // false - no new value to be assigned // CrashIfDeallocating: True - newObj has been freed or newObj does not support weak references. Bool HaveOld, bool HaveNew, bool HaveNew bool CrashIfDeallocating> static id storeWeak(id *location, Objc_object * newObj) {/ / the procedure used to update a weak reference pointer pointing to / / initialization previouslyInitializedClass pointer Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; // Use the address to index the bucket to prevent it from repeating. // Use the following operations to change the old value retry: If (HaveOld) {// change pointer to oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (HaveNew) {newTable = &SideTables()[newObj]; } else { newTable = nil; } / / lock operation, to prevent multiple threads SideTable: competition conflict: lockTwoHaveOld, HaveNew > (oldTable, newTable); If (HaveOld && *location!) {// If (HaveOld && *location! = oldObj) { SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable); goto retry; } // prevent deadlock between weak references // and ensure that the isa of all weak references is non-null to if (HaveNew && newObj) {// get the ISA pointer to the new object Class CLS = newObj->getIsa(); If (CLS! = previouslyInitializedClass && ! (CLS) (objc_class *) - > isInitialized ()) {/ / unlock SideTable: : unlockTwoHaveOld, HaveNew > (oldTable, newTable); _class_initialize(_class_getNonMetaClass(CLS, (id)newObj)); // If +initialize is in a thread // for example, +initialize is calling storeWeak and needs to manually add a protection policy to it. Mark previouslyInitializedClass = CLS and set previouslyInitializedClass pointer; // Retry goto; 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); Weak_register_no_lock method returns nil if the weak reference is released // Set the if reference flag bit in the reference count table if (newobj&&! NewObj ->setWeaklyReferenced_nolock(); weakLyReferenced_nolock (); weakLyReferenced_nolock (); *location = (id)newObj; *location = (id)newObj; } else {/ / no new value, you do not need to change} SideTable: : unlockTwoHaveOld, HaveNew > (oldTable, newTable); return (id)newObj; }Copy the code
Take a look at what this code does, aside from the various lock operations in the source code.
1), SideTable
SideTable is a structure that I named reference count and Weak reference dependency tables because it is mainly used to manage reference counts and weak tables of objects. Declare its data structure in nsobject.mm:
Struct SideTable {// Spinlock_t slock; // Hash table RefcountMap refcnts; Weak_table_t weak_table; // Weak references the global hash table weak_table_t weak_table; }Copy the code
Needless to say for the members of slock and refcnts, the first isa spinlock to prevent competing selection, and the second isa variable that helps the extra_rc co-reference count of the object’s isa pointer (for object results, mentioned in a future article). This section shows the structure and function of the weak global hash table.
2), the weak form
Weak table is a weak reference table, implemented as a Weak_table_t structure, which stores all weak reference information related to an object. It is defined as follows, note the system comment:
Weak_entry_t = value * The global weak references table. Stores object ids as keys, weak_entry_t = value Weak_entry_t structs as their values. */ struct weak_table_t {// Save weak_entry_t pointer to weak_table_t *weak_entries; // How many weak_entry_t size_t num_entries are there in weak_table_t; // Count uintptr_t mask of weak_entry_t array; // Maximum hash key offset, Weak_entry_t uintptr_t max_hash_displacement; // Use open custom method to resolve hash conflict, exceeding max_hash_displacement };Copy the code
This is a globally weak reference hash table. Use the address of an indefinite type object as the key and a Weak_entry_t type structure object as the value. Weak_entries members are, literally, weak reference table entries. The implementation is the same.
Weak_entry_t is an internal structure stored in the weak reference table, which is responsible for maintaining and storing the collection of all weak references pointing to a particular object. Its definition is as follows:
typedef objc_object ** weak_referrer_t; Struct Weak_entry_t {// All weak Pointers point to the specific object DisguisedPtrobjc_object> Referent; Weak_referrer_t *referrers; weak_referrers; weak_referrers; weak_referrers; weak_referrers; weak_referrers; weak_referrers; weak_referrers; weak_referrers; weak_referrers; uintptr_t out_of_line : 1; uintptr_t num_refs : PTR_MINUS_1; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { // out_of_line=0 is LSB of one of these (don't care which) weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }}Copy the code
In the Weak_entry_t structure, the DisguisedPtr Referent is a encapsulation of the generic object pointer to solve the memory leak problem through the generic class. Moreover, weak_entry_t and Weak_table_t both have a hash table inside, and both of them are hash conflicts solved by the open fixed value method. Out_of_line member is written from the annotation as the least significant bit. When it is 1, The Weak_REFERrer_t member will expand to a hash table. Where Weak_referrer_t is an alias for an array. What does out_of_line, num_refs, mask, max_hash_displacement do when a valid bit is in effect?
Out_of_line: flag bit. Indicates whether weak_entry_t uses an array or a hash table to store weak Pointers. Num_refs: indicates the referenced value. Weak_entry_t ->referrers array count, max_hash_displacement: weak_entry_t->referrers array count Max_hash_displacement Indicates that there is no weak_entry_t to seek in weak_entry_t. The value of out_of_line is usually equal to zero, so the weak reference table is always an array of objc_Objective Pointers, and becomes a hash table when it exceeds 4.
As a developer, it is particularly important to have a learning atmosphere and a communication circle. Here is an iOS communication group: 642363427. Welcome to join us, no matter you are small white or big bull, share BAT, Ali interview questions, interview experience, discuss technology, iOS developers exchange learning and growth together!
To summarize the StripedMap[] : StripedMap is a template class that has an array member that stores the PaddedT object, and the overloaded definition of [] returns the PaddedT value member, which is the T generic member we passed in. SideTable objects. In the array subscript, indexForPointer is used to calculate the subscript by bit operation to achieve a static Hash Table. In weak_table, its member Weak_entry will encapsulate the address of the incoming object, and there is also an entry to access the global weak reference table.
Old object unregister operation Weak_unregister_no_lock
The main function of this method is to make the old object contact the corresponding binding of weak pointer in Weak_table. According to the function name, this is called the deregister operation. From the source, it can be known that its function is to remove the binding of weak pointer from weak_table. The traversal query is aimed at multiple weak reference hash tables in Weak_entry.
New object added register operation Weak_register_NO_lock
This step is contrary to the previous step. Weak_register_no_lock function is used to register the object of the heart and complete the binding operation with the corresponding weak reference table.
Initialize weak reference object flow list
As can be seen from the above analysis, the main operations of initialization of weak reference table are fetching key, querying hash, creating weak reference table and other operations, which can be summarized as the following flow chart:
This diagram omits many cases of judgment, but the above methods are called when declaring an __weak. Of course, the storeWeak method is used not only in __weak declarations, but also in operations inside a class to manipulate weak objects.
3. When releasing, call the clearDeallocating function. The clearDeallocating function first fetches an array of weak pointer addresses based on the object’s address, then iterates through the array to set it to nil, deletes the entry from the Weak table, and clears the object’s record.
What happens to the weak pointer when the object to which the weak reference points is released? When an object is released, the basic flow is as follows:
1, call objc_release 2, execute dealloc because the reference count of the object is 0. Object_dispose is called 5, objc_destructInstance is called 6, and finally objc_clear_dealLocating is called
Focus on the objc_clear_deallocating function that is called when the object is released. This function is implemented as follows:
void objc_clear_deallocating(id obj) { assert(obj); assert(! UseGC); if (obj->isTaggedPointer()) return; obj->clearDeallocating(); }Copy the code
That is, clearDeallocating was called. Tracing back, it ended up using an iterator to get the weak table value, called Weak_clear_NO_lock, looked up the corresponding value, and empty the weak pointer. Weak_clear_no_lock function is implemented as follows:
/** * 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; } 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); }Copy the code
Objc_clear_deallocating does the following:
1. From the weak table, take dealloc object as key, find the corresponding Weak_entry_t. 2
Objc-weak. mm is a hash table where the key points to the address of the object and the Value points to the address of the weak pointer.
And when you look at the principle, it’s actually very similar to objc_setAssociatedObject in category.
Recommend a 👇 :
If you want to advance together, add a networking group