In the development process of iOS, a modifier weak is often used. The usage scenario is clear to everyone, so as to avoid the problem that strong references between objects can not be released normally and eventually lead to memory leakage. The weak keyword is used for weak references. The counter of the referenced object is not incremented by 1 and is automatically set to nil when the referenced object is released.
1. Weak
The following snippet of code is a common use of weak in development
Person *object = [Person alloc];
id __weak objc = object;
Copy the code
If you trace assembly information at this interrupt point, you can see that the underlying library is tunedobjc_initWeak
function
So what does the code for the objc_initWeak method look like?
1, objc_initWeak method
Here is the underlying source code for the objc_initWeak method
id objc_initWeak(id *location, id newObj)
{
if(! newObj) { *location = nil;return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
Copy the code
The method’s two parameters are location and newObj.
- Location: The address of the __weak pointer that stores the pointer so that the object it points to can be set to nil at the end.
- NewObj: The object referenced. Obj in this example.
As you can see from the above code, the objc_initWeak method is just an entry point to a deeper function call, inside which the storeWeak method is called. Let’s look at the implementation code for the storeWeak method.
2. StoreWeak method
The following is the implementation code for the storeWeak method.
// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
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;// 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) { // If weak PTR has a weak reference to obj before, remove the SideTable corresponding to obj and assign it to oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil; // oldTable = nil if weak PTR has not previously weakly referenced an obj
}
if (haveNew) { // If weak PTR wants weak to refer to a new obj, the SideTable corresponding to obj is removed and assigned to newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil; // If weak PTR does not need to reference a new obj, newTable = nil
}
// Lock operation to prevent multi-thread contention
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// Location should be consistent with oldObj. If it is different, the current location has already been processed by oldObj but has been modified by another thread
if(haveOld && *location ! = oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;
}
// 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())// If CLS is not already initialized, initialize it first and then try setting weak
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(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; / / the record previouslyInitializedClass here, to prevent the change if branch to enter again
goto retry; // Get newObj again, newObj should already be initialized}}// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // If weak_ptr weakly referenced other object oldObj before, weak_unregister_no_lock is called, and the weak_ptr address is removed from weak_entry_t of oldObj
}
// Assign new value, if any.
if (haveNew) { // If weak_ptr needs to weakly reference the new object newObj
Weak_register_no_lock is called, and weak_ptr's address is recorded in weak_entry_t corresponding to newObj
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
(2) Update weakly_referenced bit flag bit of Isa of newObj
// 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.
// (3) * assign weak PTR to newObj. As you can see, newObj's reference count is not +1
*location = (id)newObj; // Set weak PTR to object
}
else {
// No new value. The storage is not changed.
}
Other threads can access oldTable and newTable
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj; // Return newObj, now newObj is inferior to when it was just passed in, Weak-referenced bit position 1
}
Copy the code
The implementation code for the storeWeak method is long, but not hard to understand. Let’s analyze the implementation of this method.
storeWeak
The method actually takes five arguments, respectivelyHaveOld, haveNew and crashIfDeallocating
, which are passed in as templates and are three bool arguments. Indicates whether the weak pointer points to a weak reference before, whether the weak pointer needs to point to a new reference, and whether the weakly referenced object should crash if it is being destructed.- The method is maintained
oldTable
andnewTable
Represents the old weak reference table and the new weak reference table, respectively, which are bothSideTable
The hash table.- Called if the weak pointer had previously pointed to a weak reference
weak_unregister_no_lock
Method to remove the old weak pointer address.- Called if the weak pointer needs to point to a new reference
weak_register_no_lock
Method to add the new weak pointer address to the weak reference table.- call
setWeaklyReferenced_nolock
The weak () method modifies the bit flag of the newly referenced object
So the focus of this method is weak_unregister_NO_lock and weak_register_no_lock these two methods. Both of these methods operate on a structured variable called the SideTable, so we need to look at the SideTable first.
3, SideTable
Let’s take a look at the definition of SideTable.
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
Copy the code
SideTable is clearly defined and has three members:
- Spinlock_t slock: spinlock used to lock/unlock SideTable.
- RefcountMap refcnts: used to store reference counts for OC objects
The hash table
(This is only used if isa optimization is not turned on or the isa_T reference count overflows under ISA optimization).- weak_table_t weak_table: stores weak reference Pointers to objects
The hash table
. Is the core data structure of the weak function in OC.
3.1, weak_table_t
Let’s take a look at the underlying code of Weak_table_t.
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
Copy the code
- Weak_entries: Hash array, used to store information about weak reference objects. Weak_entry_t
- Num_entries: number of hash array elements
- Mask: the hash array length is -1, which will participate in hash calculation. Note that this is the length of the hash array, not the number of elements. For example, the array might be 64 in length and only have 2 elements.)
- Max_hash_displacement: Maximum number of hash conflicts that can occur to determine if a logical error has occurred (the number of conflicts in the hash table will never exceed the changed value)
Weak_table_t is a typical hash structure. Weak_entries are a dynamic array used to store weak_entry_t elements, which are essentially weak references to OC objects.
3.2, weak_entry_t
Weak_entry_t structure is also a hash structure, and its stored element is the pointer of the weak reference object pointer. By operating the pointer of the pointer, the weak reference pointer can point to nil after object destruction.
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // The weakly referenced object
// List of objects that reference the object, union. If the number of references is less than 4, use the inline_referrers array. When the number is greater than 4, weak_referrer_t *referrers is used
union {
struct {
weak_referrer_t *referrers; // A hash array that weakly references the address of the object pointer to the object
uintptr_t out_of_line_ness : 2; // Whether to use dynamic hash array tag bits
uintptr_t num_refs : PTR_MINUS_2; // Number of elements in the hash array
uintptr_t mask; // The hash array length is -1, which will participate in the hash calculation. Note that this is the length of the hash array, not the number of elements. For example, the array might be 64 in length and contain only 2 elements.
uintptr_t max_hash_displacement; // The maximum number of hash collisions that can occur to determine if a logical error has occurred (the number of hash collisions will never exceed the changed value)
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line(a) {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator= (const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent) Constructor, which initializes a static array
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil; }}};Copy the code
It can be seen that there is a union in weak_entry_t’s structure definition, There are fixed length array Inline_Referrers [WEAK_INLINE_COUNT] and dynamic array Weak_Referrer_t * Referrers to store the pointer address of weak reference object. Use a function method such as out_of_line() to determine which storage method to use. When the number of weak references to the object is less than or equal to WEAK_INLINE_COUNT, a fixed-length array is used. When WEAK_INLINE_COUNT exceeds WEAK_INLINE_COUNT, the elements in the fixed-length array will be transferred to the dynamic array, and then all the elements will be stored with the dynamic array.
So far we have made it clear that the structure of a weak reference table is a hash table, where Key is the address of the pointed object and Value is an array of addresses of weak Pointers. So let’s see how the weak reference table maintains this data.
4. Weak_register_no_lock method adds weak references
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 the referent is nil or if the referent uses TaggedPointer, it returns nothing
if(! referent || referent->isTaggedPointer())return referent_id;
// Make sure the referenced object is available (there is no destructor, and weak references should be supported)
bool deallocating;
if(! referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); }else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// The object being destructed cannot be weakly referenced
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
// Find the corresponding weak_entry of referent in weak_table, and add the referrer to weak_entry
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) { // If weak_entry can be found, insert the referrer into weak_entry
append_referrer(entry, referrer); // Insert the referrer into the weak_entry_t reference array
}
else { // If you can't find it, create a new one
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;
}
Copy the code
This method takes four arguments that represent the following:
- weak_table:
weak_table_t
Struct type global weak reference table.- Referent_id: weak pointer.
- *referrer_id: weak pointer address.
- CrashIfDeallocating: Whether the weakly referenced object should crash if it is being destructed.
We can see from the above code that this method mainly does the following convenient work.
- If referent is nil or referent is adopted
TaggedPointer
Count mode, return directly, do nothing.- Throws an exception if the object is being destructed.
- If the object cannot be referenced by weak, return nil.
- Called if the object is not redestructed and can be referenced weak
weak_entry_for_referent
Method finds the corresponding Weak_entry from the weak reference table according to the address of the weak reference object, and calls it if it can find itappend_referrer
The weak pointer address is inserted into the. Otherwise, create a Weak_entry.
4.1. Weak_entry_for_referent takes elements
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; Weak_table ->mask is used to ensure that the index does not cross the boundary
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); // Triggered a bad weak table crash
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) { // When hash conflicts exceed possible Max hash conflicts, the element is not in the hash table, and nil is returned
returnnil; }}return &weak_table->weak_entries[index];
}
Copy the code
4.2. Append_referrer adds an element
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) { // If Weak_entry does not already use dynamic arrays, go here
// 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; }}// If the inline_referrers location is already full, then the inline_referrers will be converted to the dynamic array.
// 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;
}
// For dynamic array add-ons:
assert(entry->out_of_line()); // Assert: the dynamic array must be used at this time
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // If the number of elements in the dynamic array is greater than or equal to 3/4 of the total array space, then the array space is doubled
return grow_refs_and_insert(entry, new_referrer); // Expand and insert
}
// If expansion is not required, directly insert weak_entry into weak_entry
Weak_entry is a hash table. Key: w_hash_pointer(new_referrer) value: new_referrer
// Careful people may notice that the hash algorithm of weak_entry_t is the same as that of weak_table_t, and the expansion/reduction algorithm is also the same
size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' ensures that the begin position can only be greater than or equal to the length of the array
size_t index = begin; // The initial hash index
size_t hash_displacement = 0; // Is used to record the number of hash collisions, that is, the number of hash reshifts
while(entry->referrers[index] ! = nil) { hash_displacement++; index = (index+1) & entry->mask; // index + 1, move to the next position, try again can insert. The value of entry->mask must be 0x111, 0x1111, 0x11111... , because the array grows by *2 each time, i.e. 8, 16, 32, corresponding to the mask of the dynamic array space length -1, which is the previous value.
if (index == begin) bad_weak_table(entry); // index == begin means that the array has gone around without finding the right place, and there must be something wrong.
}
if (hash_displacement > entry->max_hash_displacement) { Max_hash_displacement means that we can try max_hash_displacement at most and be sure to find the hash position of object
entry->max_hash_displacement = hash_displacement;
}
// store ref into hash array and update the number of elements num_refs
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
Copy the code
This code first determines whether to use a fixed-length array or a dynamic array. If a fixed-length array is used, the weak pointer address is simply added to the array. If the fixed-length array is exhausted, the elements of the fixed-length array need to be transferred to the dynamic array.
Weak_unregister_no_lock removes the reference
If the weak pointer previously pointed to a weak reference, the weak_unregister_no_lock method is called to remove the old weak pointer address.
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))) { // Find weak_entry_t corresponding to referent
remove_referrer(entry, referrer); // Remove the referrer from the hash array of weak_entry_t corresponding to the referent
// After removing the element, check whether the Weak_entry_t hash array is empty
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) { // If the hash array of weak_entry_t is empty, you need to remove weak_entry_t from weak_tableweak_entry_remove(weak_table, entry); }}Copy the code
- First, it finds the referent’s corresponding Weak_entry_t in the Weak_table
- Remove the referrer from weak_entry_t
- After removing the element, determine whether there are still elements in weak_entry_t (empty==true?).
- If weak_entry_t has no elements at this point, you need to remove weak_entry_t from Weak_table
The reference count does not increase by 1. How do all Pointers to weak references automatically set to nil when the object is released?
6, dealloc
When the reference count of an object is 0, the underlying _objc_rootDealloc method is called to release the object, and the rootDealloc method is called in the _objc_rootDealloc method. The following is the code implementation of the rootDealloc method.
inline void
objc_object::rootDealloc(a)
{
if (isTaggedPointer()) return; // fixme necessary?
if(fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present());free(this);
}
else {
object_dispose((id)this); }}Copy the code
- First determine whether the object is
Tagged Pointer
If yes, return directly.- If the object uses the optimized ISA counting mode and the object is not referenced by weak
! isa.weakly_referenced
, there is no associated object! isa.has_assoc
There is no custom C++ destructor! isa.has_cxx_dtor
SideTable is not used for reference counting! isa.has_sidetable_rc
Is directly released quickly.- If the condition in 2 is not met, it is called
object_dispose
Methods.
6.1, object_dispose
The object_Dispose method is simple and primarily calls the objc_destructInstance method internally.
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;
}
Copy the code
The above code is clear. If there is a custom C++ destructor, the C++ destructor is called. If there is an associated object, the associated object is removed and itself removed from the Association Manager map. Call the clearDeallocating method to remove the associated reference to the object.
6.2, clearDeallocating
inline void
objc_object::clearDeallocating(a)
{
if(slowpath(! isa.nonpointer)) {// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.clearDeallocating_slow(); } assert(! sidetable_present()); }Copy the code
ClearDeallocating has two branches that determine whether the object is optimized for ISA reference counting, or if it isn’t, they need to clean up the reference count data stored in SideTable. Isa.has_sidetable_rc) or weak references (ISa.weakly_referenced). Weakly_referenced Call the clearDeallocating_slow method.
6.3, clearDeallocating_slow
NEVER_INLINE void
objc_object::clearDeallocating_slow(a)
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this]; // In the global SideTables, use this pointer as the key to find the corresponding SideTable
table.lock();
if (isa.weakly_referenced) { // If obj is weakly referenced
weak_clear_no_lock(&table.weak_table, (id)this); // Clean this in weak_table of SideTable
}
if (isa.has_sidetable_rc) { // If SideTable is used for reference counting
table.refcnts.erase(this); // Remove this from the reference count of SideTable
}
table.unlock();
}
Copy the code
What we are interested in here is the Weak_clear_NO_lock method. Weak_clear_no_lock is called here to clean up weak_table.
6.4, weak_clear_no_lock
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); // Find referent's corresponding weak_entry_t in weak_table
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;
// Find the address array of the weak pointer to the referent and the length of the array
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]; // Get the address of each weak PTR
if (referrer) {
if (*referrer == referent) { // If the weak PTR does refer to the referent, set the weak PTR to nil. This is why the weak pointer is automatically set to nil
*referrer = nil;
}
else if (*referrer) { // If the stored weak PTR does not refer to the weak referent, this may be due to a logic error in the Runtime code
_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); // Since the referent is being released, the referent's weak_entry_t should also be removed from the weak_table
}
Copy the code
7,
- 1. The principle of weak is that a hash table of weak_table_t structure is maintained at the bottom level, where key is the address of the object and value is the address array of weak Pointers.
- The weak keyword is used for weak references. The counter of the referenced object is not incresed by 1 and is automatically set to nil when the referenced object is released.
- 3. Call when the object is released
clearDeallocating
The function retrieves an array of weak pointer addresses based on the address of the object, iterates through the array to set the data to nil, deletes the entry from the weak table, and clears the record of the object. - 4. In the article, three structures such as SideTable, Weak_table_t and Weak_entry_t are introduced. The relationship between them is shown in the figure below.
The resources
- SideTables, SideTable, weak_table, weak_entry_t
- Weak references the underlying implementation principle