NSObject. Mm source

– # # # # # # id

typedef struct objc_object *id;
struct objc_object {
    isa_t _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
Copy the code

###### ISA_T structure in arm64 architecture (the bits format is the same, some bits are different)

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULLstruct { uintptr_t nonpointer : 1; // uintptr_t has_assoc: 1; Uintptr_t has_cxx_dtor: 1; Uintptr_t shiftcls: 33; // // Uintptr_t magic: 6; // Fixed value 0xd2, used to tell if an object is not initialized during debugging. uintptr_t weakly_referenced : 1; // Whether the object has an weak object, if not, the uintptr_t deallocating: 1 is faster; Uintptr_t has_sidetable_rc: 1; Uintptr_t extra_rc: 19; // Store the result of subtracting the reference count by one# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
    };
};
Copy the code

##### Reference count

Nonpointer (bits. Extra_rc + SideTable) manages reference counts

Nonpointer: On 64-bit systems, in order to reduce memory usage and improve performance, a portion of the ISA field is used to store other information.

######RetainCount source code, directly reflects the three cases

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return(uintptr_t)this; // 1, TaggedPointer sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits);if// 3, Nonpointer -- (bits. extra_rc + SideTable) uintptr_t rc = 1 + bits.extra_rc;if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    returnsidetable_retainCount(); // 2, SideTable hash table}Copy the code

SideTable hash table

Memory management main structure code

struct SideTable { spinlock_t slock; RefcountMap refcnts; // Reference-countedhashTable weak_table_t weak_table; // weak references globallyhashTable};Copy the code

RetainCount is stored in an unsigned integer

uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];
    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if(it ! Refcnt_result += it->second > SIDE_TABLE_RC_SHIFT; } table.unlock();return refcnt_result;
}
Copy the code

# # # # retain the source code

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISAassert(! isa.nonpointer);#endifSideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; // The reference count is too large to be managedif(! (refcntStorage & SIDE_TABLE_RC_PINNED)) {// add 1 to the reference count, SIDE_TABLE_RC_ONE = 1<<2) refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock();return (id)this;
}
Copy the code

# # # # release the source code

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISAassert(! isa.nonpointer);#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if(it == table.refcnt.end ()) {// (1) releasing do_dealloc =true; Table. Refcnts [this] = SIDE_TABLE_DEALLOCATING; }else if(it->second < SIDE_TABLE_DEALLOCATING) {// (2) Reference count is 0 do_dealloc =true; The second / / tag dealloc (pictured above) it - > second | = SIDE_TABLE_DEALLOCATING; }else if(! (it->second & SIDE_TABLE_RC_PINNED) {// (3) normal reference count minus 1, (figure above shows, offset by 2 digits, SIDE_TABLE_RC_ONE = 1<<2) it->second -= SIDE_TABLE_RC_ONE; } table.unlock();if(do_dealloc &&performDealloc) {// (4) do_dealloc is true, Void (*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); }return do_dealloc;
}
Copy the code

Look at the next few judgments. If the object is last in the reference count table, do_dealloc is set to true, and the reference count value is SIDE_TABLE_DEALLOCATING (binary 00000010).

If the reference count is smaller than SIDE_TABLE_DEALLOCATING, it is 0.

SIDE_TABLE_RC_ONE = side_table_one; SIDE_TABLE_RC_ONE = side_table_one; Is 1.

4. Finally, if do_dealloc and performDealloc (already true when passed in) are both true, execute SEL_dealloc to release the object. Method returns do_dealloc.

5. Call the parent class Dealloc up to the root class NSobject

Extra_rc + SideTable) ####retain source code

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if(slowpath(! Newisa.nonpointer) {// 2, SideTable hash method ClearExclusive(& ISa.bits);if(! tryRetain && sideTableLocked) sidetable_unlock();if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        if(slowpath(tryRetain && newisa.deallocating) {// Releasing ClearExclusive(& ISa.bits);if(! tryRetain && sideTableLocked) sidetable_unlock();returnnil; } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); Extra_rc ++ // If newisa.extra_rc++ overflows, carry==1ifSlowpath (carry) {// overflowif(! handleOverflow) { ClearExclusive(&isa.bits); // Empty operation (system reserved)returnrootRetain_overflow(tryRetain); // Call rootRetain(tryRetain,YES)} // call rootRetain_overflow here, just save half of the value of extra_rc into SideTableif(! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked =true;
            transcribeToSideTable = true; newisa.extra_rc = RC_HALF; // Overflow, set to half, save half to SideTable newisa.has_sidetable_rc =true; // StoreExclusive saves newISa. bits to ISA. bits, returns YES on successwhileJudge once is over}while(slowpath(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));if(slowpath(transcribeToSideTable)) {// Save: save RC_HALF (half) into SideTable sidetable_addExtraRC_nolock(RC_HALF); }if(slowpath(! tryRetain && sideTableLocked)) sidetable_unlock();return (id)this;
}
Copy the code

The extra_rc++ method is simple: 1. 2. The extra_rc is fully loaded with half of the stock in SideTable (arm64’s EXTRA_RC is 19 bits, fully stocked)

###### debit saving method

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be sethere assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); // System count limit, directtrue
    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);

    if(carry) {// If the borrow is stored here and overflows, As SIDE_TABLE_RC_PINNED number (32 or 64 largest numerical digits - 1) refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false; }}Copy the code

# # # # # release the source code

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if(slowpath(! Newisa.nonpointer)) {// This is ClearExclusive(&isa.bits);if (sideTableLocked) sidetable_unlock();
            returnsidetable_release(performDealloc); } uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // if extra_rc==0, extra_rc-- is negative, carry=1if(slowpath(carry)) { goto underflow; }}while(slowpath(! StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)));if (slowpath(sideTableLocked)) sidetable_unlock();
    return false; Underflow: // newisa = oldisa;if(slowpath(newisa.has_sidetable_rc)) {// With a borrow save, rootRelease_underflow re-enters the functionif(! handleUnderflow) { ClearExclusive(&isa.bits);returnrootRelease_underflow(performDealloc); } // Some lock operationsif(! sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked =true; goto retry; } // Retrieve the number of borrowed references (maximum number of borrowed references RC_HALF) size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);if(borrowed > 0) { // extra_rc-- newisa.extra_rc = borrowed - 1; If &isa.bits and oldisa.bits are equal, then assign the value of newISa. bits to &ISa. bits and returntrue
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if(! Stored) {// Failed to save, try again (duplicate code) 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) {// Failed, put the number of times back into SideTable, retry sidetable_addExtraRC_nolock(borrowed); goto retry; } // release end sidetable_unlock();return false;
        }
        else{// If sideTable also has no count, then we go to the dealloc stage below}} // If there is no borrow save count, we go hereif(slowpath(newisa.deallocating)) {// If the object is already being released, error warning: release ClearExclusive(& ISa.bits) multiple times;if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
    }
    newisa.deallocating = true; / / save bitsif(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;if (slowpath(sideTableLocked)) sidetable_unlock();

    __sync_synchronize();
    if(performDealloc) {call dealloc ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); }return true;
}
Copy the code

# # # # NSobject dealloc source code

void *objc_destructInstance(id obj) 
{
    if (obj) {
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
    return obj;
}
Copy the code

Object_cxxDestruct does three simple things: 1. Execute object_cxxDestruct and process the member variables of this class (the parent is handled by the parent). Object_cxxDestruct contains the final for (; cls; CLS = CLS ->superclass){execute. Cxx_destruct}; Cxx_destruct finally executes to process the member variable: Strong objc_storeStrong(&ivar, nil)release object, ivar assign nil, ② weak objc_destroyWeak(&ivar) destroy the ivAR address in the weak table.

2. Execute _object_remove_assocations to remove the object from the assocate category.

3. Perform objc_clear_deallocating, clearing the reference-count table and the weak reference table, locating all weak references to nil (which is where the weak variable can be safely empty).

#### A simple summary of the object release process -dealloc

Extra_rc triggers dealloc by subtracting 0, * marking the object Isa.deallocating =true* There can be no new __weak references * calls to [self dealloc] (MRC needs to manually release strongly referenced variables in the dealloc method) * Calls to -dealloc for each level of the parent class in the inheritance relationship. All the way to the root class (usually NSObject) 2. The NSObject call -dealloc * does just one thing: call object_Dispose () in the Objective-C Runtime 3. Call object_dispose() * objc_destructInstance(obj); * free(obj); 4. Objc_destructInstance (obj) performs three operations *if(cxx) object_cxxDestruct(obj); // Release (1) strong objc_storeStrong(&ivar, nil)release object, ivar assigned nil, (2) weak ivar, out of scope, Objc_destroyWeak (&ivar) >> storeWeak(&ivar, nil) removes the address of ivar pointing to nil from the object's weak table. *if(assoc) _object_remove_assocations(obj); // Remove Associate data (that's why you don't need to manually remove it) * obj->clearDeallocating(); // Empty the reference count table, empty the weak variable table and point all references to nilCopy the code

Objc_storeStrong source

Reassign to a strong pointer, for example

NSObject *obj = [NSObject new]; NSObject *obj2 = [NSObject new]; NSObject *strongObj = obj; strongObj = obj2; // execute objc_storeStrong(&strongObj, obj2); { NSObject *obj = [NSObject new]; } // out of scope, do objc_storeStrong(&obj, nil);Copy the code
void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}
Copy the code

Retain the new object and repoint the strong pointer to the new object. Release the old object.

Reference: iOS Advanced — iOS (Objective-C) memory management · Dealloc process under 2 ARC and the exploration of. Cxx_destruct