When I wrote “iOS (Objective-C) Memory Management & Blocks,” I didn’t find that the NSObject code was already open source, so I analyzed the GNUStep source code and guessed at the Apple part.

Essentially, the implementation of NSObject is open source in ObjC4-706. So I started learning about objC4.

Here’s a look at some of Apple’s NSObject memory management.

SideTable

Go to nsobject.mm and start with some very important information for later understanding.

objc4-706 NSObject.mm SideTable:

struct SideTable {
    // Ensure the atomic operation of the spin lock
    spinlock_t slock;
    // Reference count hash table
    RefcountMap refcnts;
    // weak references the global hash table
    weak_table_t weak_table;
};
Copy the code

The SideTable structure sets several very important variables for weight.

// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE            (1UL<<2)  // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED         (1UL<<(WORD_BITS-1))

#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
Copy the code

Several important offsets are defined above. The reference count retainCount is stored in an unsigned integer that is 8 bytes long. Its structure can be shown as follows:

  1. SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)(represents the first bit of the memory where the object resides), indicating whether the object has a weak object.
  2. SIDE_TABLE_DEALLOCATING (1UL<<1) (represents the second bit of memory in which the object resides), indicating whether the object is dealloc (destructor).
  3. SIDE_TABLE_RC_ONE (1UL<<2)(represents the third bit of memory in which the object resides), which stores the reference count value (in fact, the third bit is used to store the reference count value).

retainCount

Find the implementation of retainCount and look down layer by layer.

objc4 NSObject.mm retainCount:

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}
Copy the code

objc4 objc-object.h rootRetainCount:

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;
    return sidetable_retainCount();
}
Copy the code

objc4 NSObject.mm sidetable_retainCount:

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 ! = table.refcnts.end()) {// this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}
Copy the code

It ->second refers to the 8-bit unsigned integer that holds the reference count.

Several important offsets in Sidetable were described above, and the true reference count can be obtained by shifting SIDE_TABLE_RC_SHIFT.

So, the main thing in sidetable_retainCount() is to traverse the reference count table looking for objects to get the reference count +1 and return the result.

retain

Find the implementation of retain.

objc4 NSObject.mm retain:

- (id)retain {
    return ((id)self)->rootRetain();
}
Copy the code

objc4 objc-objc.h rootRetain:

// Base retain implementation, ignoring overrides.
// This does not check isa.fast_rr; if there is an RR override then 
// it was already called and it chose to call [super retain].
inline id 
objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}
Copy the code

objc4 NSObject.mm sidetable_retain:

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISAassert(! isa.nonpointer);#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
Copy the code

RefcntStorage += SIDE_TABLE_RC_ONE We illustrate by distance:

If obj had a reference count value of 1 (binary 00000100, because the first digit and the second digit are used to identify other things) and now if you want to retain and increment the reference count value by 1, then 00000100 => 00001000 is required. So it’s actually retainCount + 4 from an integer point of view, not +1 as we understand it.

RefcntStorage += SIDE_TABLE_RC_ONE; SIDE_TABLE_RC_ONE = SIDE_TABLE_RC_ONE; .

release

objc4-706 NSObject.mm release:

- (oneway void)release {
    ((id)self)->rootRelease();
}
Copy the code

objc4-706 objc-object.h rootRelease:

inline bool 
objc_object::rootRelease()
{
    if (isTaggedPointer()) return false;
    return sidetable_release(true);
}
Copy the code

objc4-706 NSObject.mm sidetable_release:

// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa 
// -release paths all return zero in eax
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.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}
Copy the code

Look at the next few judgments.

  1. If the object is recorded last in the reference count table:do_deallocSet it to true and the reference count value to SIDE_TABLE_DEALLOCATING (binary 00000010).
  2. If the 8-bit reference count is smaller than SIDE_TABLE_DEALLOCATING (binary 00000010), it would be 00000001 or 00000000:do_deallocSet it to true and add the DealLocating identifier. (but as for what use do not quite understand, I hope which god to point out).
  3. If you have already8-bit reference count & SIDE_TABLE_RC_PINNED, the object was not deallocating, wasn’t weakly referenced, and the 8-bit mark didn’t overflow: the 8-bit reference count was reduced by 4, which is the true reference count value -1.
  4. And finally, ifdo_deallocperformDeallocIf both are true, execute SEL_dealloc to release the object.
  5. Method returns do_dealloc.

If you just want to know ARC reference count correlation, just look at the code above. Alloc and dealloc are mostly memory allocations for objects.


alloc

View alloc code.

objc4-706 NSObject.mm alloc:

+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code

objc4-706 NSObject.mm _objc_rootAlloc:

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code

objc4-706 NSObject.mm callAlloc:

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if(slowpath(checkNil && ! cls))return nil;

#if __OBJC2__
    if(fastpath(! cls->ISA()->hasCustomAWZ())) {// No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if(slowpath(! obj))return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if(slowpath(! obj))return callBadAllocHandler(cls);
            returnobj; }}#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}
Copy the code

Slowpath (checkNil &&! cls)) return nil; Judgment.

objc4-706 objc-os.h slowpath:

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
Copy the code

The __builtin_expect(exp, n) method means that exp is most likely zero and returns exp. You can think of fastPath (x) as true and slowpath(x) as false.

So, checkNil is false, checkNil &&! CLS is also false. So this is not going to return nil. Read on.

This is followed by an Objective-C 2.0 conditional compilation instruction. Of course, what we’re using now is objtive -C 2.0 and will execute the code. First make a judgment if (fastPath (! cls->ISA()->hasCustomAWZ()))… else … , which determines whether a class has a custom +allocWithZone implementation.

If there is no custom +allocWithZone implementation. If (fastPath (CLS ->canAllocFast()))… else … Is true only if the object does not exist, there is no ISA, etc. So look at the else in between.

Id obj = class_createInstance(CLS, 0); . Note that the Coptyright at the top of objc-Runtime-new. h is Copyright (c). 2005-2007 Apple Inc. All Rights Reserved.)

CanAllocFast () in objc-Runtime-new.mm is defined as follows:

objc4-706 objc-runtime-new.h canAllocFast:

#if FAST_ALLOC
    bool canAllocFast() {
        return bits & FAST_ALLOC;
    }
#else
    bool canAllocFast() {
        return false;
    }
#endif
Copy the code

Looking at the FAST_ALLOC definition again, look at the following image:

#elif 1 intercepts define directly, so #if FAST_ALLOC doesn’t work. So, canAllocFast() returns false and fastPath (CLS ->canAllocFast()) is judged to be false.

perform

// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if(slowpath(! obj))return callBadAllocHandler(cls);
return obj;
Copy the code

objc4 objc-runtime-new.mm class_createInstance:

id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
Copy the code

objc4 objc-runtime-new.mm _class_createInstanceFromZone:

id 
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
    void *bytes;
    size_t size;

    // Can't create something for nothing
    if(! cls)return nil;

    // Allocate and initialize
    size = cls->alignedInstanceSize() + extraBytes;

    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;

    if (zone) {
        bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        bytes = calloc(1, size);
    }

    return objc_constructInstance(cls, bytes);
}
Copy the code

Bytes = calloc(1, size); And return objc_constructInstance (CLS, bytes); . Bytes is the memory required by the object.

FYI: calloc(size_t __count, size_t __size) is a C method used to allocate n consecutive sizes of memory in the dynamic storage area. The function returns a pointer to the starting address of the allocation. If the assignment is unsuccessful, NULL is returned.

objc_constructInstance(Class cls, void *bytes) 
{
    if(! cls || ! bytes)return nil;

    id obj = (id)bytes;

    obj->initIsa(cls);

    if (cls->hasCxxCtor()) {
        return object_cxxConstructFromClass(obj, cls);
    } else {
        returnobj; }}Copy the code

The objc_constructInstance method defines bytes (Pointers to sub-objects) as obj and assigns the isa of OBj to the CLS passed in. Finally, we return to OBJ.

FYI: hasCxxCtor() is used to determine whether the current class or superclass has an implementation of the.cxx_construct constructor. HasCxxDtor () is an implementation of the.cxx_destruct method that determines whether the current class or superclass has one. Reference: Objc objects in this life

dealloc

Find the implementation of dealloc.

objc4 NSObject.mm dealloc:

- (void)dealloc {
    _objc_rootDealloc(self);
}
Copy the code

objc4 NSObject.mm _objc_rootDealloc:

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}
Copy the code

objc4 NSObject.mm _objc_rootDealloc:

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;
    object_dispose((id)this);
}
Copy the code

FYI: Understand Tagged Pointer in depth

objc4 objc-runtime-new.mm object_dispose:

id 
object_dispose(id obj)
{
    if(! obj)return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
Copy the code

objc4 objc-runtime-new.mm objc_destructInstance:

/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. **********************************************************************/
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

objc4 objc-object.h clearDeallocating:

inline void 
objc_object::clearDeallocating()
{
    sidetable_clearDeallocating();
}

Copy the code

objc4 NSObject.mm sidetable_clearDeallocating:

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if(it ! = table.refcnts.end()) {if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}
Copy the code

So, objc_destructInstance (obj); Destroying the instance without freeing memory, calling the C++ destructor (if the object has one), processing the first closed object (if it has one), and finally calling obj->clearDeallocating(). Clear weak references and excess retain count. objc_destructInstance(obj); After is free (obj); Frees the memory occupied by OBj.

#Other

(when writing this article, my biggest feeling is that I don’t understand C++.)

All information for this post:

Tracy Wang- ARC(Part 1)

Turns out I’m not unhappy. – What’s going on with our date

Desgard-weak Specifies how weak references are implemented

Objc Objects of this life

Objective-c reference counting principle

Objective-c Runtime first day of admission — ISA and Class

Sindrilin – Chatter memory management