In previous articles, you’ve gotten a glimpse of reference counting and its use in development. In this article, we’ll take a closer look at apple’s underlying implementation of reference counting.

Objc source code involved in this article, from objC source code, version is 723, the article involves a large number of source code, are deleted, mainly on the lock part of the deletion.

Takeaway:

  1. Interpretation of the source code, if you feel redundant source code, each step of the interpretation of the final summary, you can directly see the summary.
  2. Part 5 – Graphs summarizes graphs related to reference counting.

Storage of reference counts

We know that for variables of scalar type, there is no reference count, because they are not objects, and their requested variables are stored on the stack, which is managed by the system.

Another type is Tagged Pointer, which includes variables of NSNumber, NSString, and NSDate. They are also stored on the stack, and of course there is no reference count.

The following table is sorted out:

Reference counting for object types exists in two places:

1.1 Reference counting in ISA Pointers

First, let’s look at the optimized ISA pointer stored in the object type. Here is the isa pointer layout that appears multiple times:

1.2 Reference count in Side Table

The structure of Side Table in the system is as follows:

The section of each Side Table for reference counting is as follows:

Reference count management

Once you know where reference counts are stored, it becomes easier to understand reference count management.

2.1 Methods for managing reference counts

Below, for the above method, according to the source code, a certain trace.

2.2 retainCount

The subsequent reference count correlation method looks only at the optimized pointer, which is described only for 64 bits.

According to the source code, the call trajectory is as follows:

Nsobject.mm ━retainCount() l – rootRetainCount() L – rootRetainCount()

inline uintptr_t objc_object::rootRetainCount() { // 1. If (isTaggedPointer()) return (uintptr_t)this sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits); If (bits.nonpointer) {// Optimized ISA pointer //2. 1 uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { //3. Rc += sidetable_getExtraRC_nolock(); rc += sidetable_getExtraRC_nolock(); } sidetable_unlock(); return rc; } sidetable_unlock(); return sidetable_retainCount(); }Copy the code

RetainCount summary

Among them, for the latter two cases:

For the reference count that exists in the Side Table, note that the lowest two bits are occupied, so when fetching it, move it two bits to the right.

2.3 retain

The source code call path is as follows:

Nsobject. mm ━retain() L ━ OBJc_retain () L ━ OBj ->retain() L ━rootRetain()

ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    // 1. Tagged Pointer is returned directly
    if (isTaggedPointer()) return (id)this;
    bool sideTableLocked = false;
    // transcribeToSideTable is used to indicate whether extra_rc is overflowing. The default is false
    bool transcribeToSideTable = false;
    isa_t oldisa;
    isa_t newisa;
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);   // extract isa_t
        newisa = oldisa;
        // If the object is destructing, return nil
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if(! tryRetain && sideTableLocked) sidetable_unlock();return nil;
        }
        uintptr_t carry;
        The extra_rc+1 pointer in isa
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            Extra_rc can no longer be stored in the ISA pointer
            // newisa.extra_rc++ overflowed
            if(! handleOverflow) {// If the overflow is not handled, it will be recursively called once here, and then when it comes in
                // handleOverflow is set to true by rootRetain_overflow, which directly enters the following overflow handler
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }

            /* Overflow handling: 1. First halve the reference count in extra_RC and continue to store it in ISA. 2. Set has_sideTABLE_rc to true, indicating that Side Table storage is borrowed. 3. Store the other half of the reference count in the Side Table
            if(! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked =true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true; }}while(slowpath(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));// Replace oldisa with newisa and assign the value to ISa.bits (update ISA_t). If this fails, do while trying again

    if (slowpath(transcribeToSideTable)) {
        // 3. Isa extra_rc overflows to place half of the refer count values in the Side Table (RC_HALF = 18, extra_rc = 19 bits)
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }
    if(slowpath(! tryRetain && sideTableLocked)) sidetable_unlock();return (id)this;
}
Copy the code

Retain summary:

Tagged Pointer object with no retain.

Extra_rc in ISA adds 1 if it does not overflow. If overflow occurs, isa and side table are stored in half.

2.4 release

Source code call path:

Nsobjl.mm ━ RELEASE () L ━ objc_release() L ━ obj->release() L ━ rootRelease() l

Here is the main execution flow of the Release method:

ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // 1. If it is Tagged Pointer, return false without dealloc
    if (isTaggedPointer()) return false;
    isa_t oldisa;
    isa_t newisa;
 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        uintptr_t carry;
        // 2. Reduce the reference count stored in the current ISA pointer by 1. If it does not overflow, return false, dealloc is not required
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // 3. If subtracting 1 leads to the bottom overflow, it needs to borrow from Side Table to jump to the bottom overflow processing
            gotounderflow; }}while(slowpath(! StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)));return false;
 underflow:
    newisa = oldisa;
    if (slowpath(newisa.has_sidetable_rc)) {
        // Side Table has a reference count
        if(! handleUnderflow) {// If no overflow is handled, this will only be called once, and handleUnderflow will be set to true by rootRelease_underflow,
            // Enter the overflow processing process directly
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }
        // 3.1 Try to fetch half of the maximum number of reference counts that ISA can store from the Side table
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
        // If the fetch reference count is greater than 0
        if (borrowed > 0) {
            // 3.2 Remove reference count -1 and save to ISA, return false on success (not destroyed)
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if(! stored) {// 3.2.1 Failed to save to ISA, try again
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed- 1), 0, &overflow);
                    if(! overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); }}}if(! stored) {3.2.2 If the save fails again, the borrowed half retain count will be returned to the Side Table
                // And try again from 2
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }
            return false;
        } else {
            // Side table is empty after all. Fall-through to the dealloc path.}}// 4. After the above process is complete, you need to destroy the object and call dealloc
    if (slowpath(newisa.deallocating)) {
        // Object is being destroyed
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))goto retry;
    __sync_synchronize();
    // Destroy the object
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}


// Equivalent to [this autorelease], with shortcuts if there is no override
inline id 
objc_object::autorelease(a)
{
    if (isTaggedPointer()) return (id)this;
    if(fastpath(! ISA()->hasCustomRR()))return rootAutorelease();

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}
Copy the code

Summary of the release:

Destruction of objects

3.1 dealloc method

3.2 dealloc rewrite

3.3 dealloc source

The source code execution process is as follows:

━ DEALLOc L ━_objc_rootDealloc — < nSObject. mm> L ━ rootDealloc — < objC-object. h> L ━ OBJect_dispose — <objc-runtime-new.mm> L ━ objc_destructInstance, free — <objc-runtime-new.mm>

We trace the method rootDealloc:

inline void objc_object::rootDealloc(a)
{
    if (isTaggedPointer()) return;  // fixme necessary?
    // if there isa non-pointer isa, that is, an optimized isa
    // if there are no associated objects, weak applications, c++ destructors, or references to side_table, free is removed.
    if(fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present());free(this);
    } 
    else {
        // Otherwise, enter the destruction process
        object_dispose((id)this); }}Copy the code

Here is the process for destroying specific objects:

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);   // Clear member variables
        if (assoc) _object_remove_assocations(obj); // Remove the associated object
        obj->clearDeallocating();   // Set the weak pointer to the current to nil
    }

    return obj;
}
Copy the code

Yes, the next step is to go to the clearDeallocating() method.

Fourth, the weak

The dealloc method uses the dealloc method to specify that all Pointers to the weak object are set to nil.

4.1 weakPointers store object structures

To understand the handling of the weak pointer, you need to understand its object structure.

Weak Pointers are stored in weak_table, which is a hash Table, in each Side Table object.

Later, in weak_table, the entry table of each object is stored, which is also a hash table, and each entry stores all weak reference addresses of the object to be destroyed.

There is a group of Side tables in the system. You can obtain the corresponding SideTable based on the object address.

As shown below:

4.2 weakHash table for pointer storage

The structure of the weak pointer is shown in the following figure, which shows how the weak pointer is stored by concatenating the hash table.

4.3 the source code

Tracing the clearDeallocating() method above, based on the optimized ISA pointer, it calls:

━ clearDeallocating() — <objc-object.h> L ━ clearDeallocating_slow() — < nsobject.mm >

NEVER_INLINE void objc_object::clearDeallocating_slow(a)
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        // The object has an weak pointer and will all be set to nil
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        // Reference counts are stored in the Side Table and erased
        table.refcnts.erase(this);
    }
    table.unlock();
}
Copy the code

Take a closer look at how the weak pointer is cleared.

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
    //1. Get the pointer to the destroyed object
    objc_object *referent = (objc_object *)referent_id;
    //2. Search object pointer using hash to find the corresponding entry in the Weak_table
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        return;
    }
    // 3. Set all references to nil
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        // 3.1. If there are more than 4 weak references, set all weak references in the referrers array to nil.
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } else {
        Set all weak references in the inline_referrers array to nil for no more than 4
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    // Loop sets all references to nil
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if(*referrer == referent) { *referrer = nil; }}}// 4. Remove the entry corresponding to the referent_id from the Weak_table
    weak_entry_remove(weak_table, entry);
}
Copy the code

Weak pointer processing summary:

Five, the figure

5.1 Object Structure Diagram

Figure 5.2 storage

reference

link

  1. Advanced Memory Management Programming Guide
  2. Memory Management Programming Guide for Core Foundation
  3. Automatic Reference Counting
  4. Objective-C Automatic Reference Counting (ARC)
  5. Friday Q&A 2010-07-16: Zeroing Weak References in Objective-C
  6. Friday Q&A 2017-09-22: Swift 4 Weak References
  7. How does the Runtime implement the weak attribute
  8. Objective-c reference counting principles
  9. IOS manages the data structure and operation algorithm of object memory –SideTables, RefcountMap, Weak_table_T-1
  10. IOS manages the data structure and operation algorithm of object memory –SideTables, RefcountMap, Weak_table_T-two
  11. Objc source