This article begins with the exploration of iOS memory management, mainly involving the contents of 1. Memory management solution: Tagged Pointer, NONPOINTER_ISA, and SiddeTables 3. ARC&MRC: retain and Release, and retainCount 4. Automatic releasepool: AutoReleasepool 5. Weak reference weak implementation principle

  • IOS Memory Management 1: Tagged Pointer& Reference count
  • IOS Memory Management 2: Automatic releasepool Autoreleasepool

1. Memory layout

In iOS, the memory layout area is roughly divided into five areas: stack area, heap area, BSS segment, data segment and code segment. Their distribution in memory is shown as follows:

  • Stack area: automatically allocated by the compiler, managed by the system, automatically cleared when no longer needed. Local variables and function parameters are stored here. The memory address of the stack area usually starts with 0x7.
  • The heap areaThose by:new.alloc,block copyThe objects created are stored here, managed by the developer, and need to tell the system when to free memory. Under ARC, the compiler automatically frees memory when appropriate, whereas under MRC, the developer needs to manually free the memory. The memory address of the heap usually starts with 0x6.
  • BSS segment: The BSS segment is also called the static segment. It is used to store uninitialized global variables. The data in memory always exists during the running of the program, and will be released by the system after the program ends.
  • Data segment: Data segment, also known as constant area, is dedicated to storing constants, which will be released by the system after the program ends.
  • Code snippet: An area of program code used to hold code that is compiled into binary and stored in memory while a program is running.

It’s worth noting that the scope of a static variable has nothing to do with objects, classes, or categories, but only files.

static int age = 10;

@interface Person : NSObject
-(void)add;
+(void)reduce;
@end

@implementation Person

- (void)add {
    age++;
    NSLog(@"The Person inside: % % @ - p - % d", self, &age, age);
}

+ (void)reduce {
    age--;
    NSLog(@"The Person inside: % % @ - p - % d", self, &age, age);
}
@end


@implementation Person (DS)

- (void)ds_add {
    age++;
    NSLog(@"Person (DS) inside :%@-%p--%d", self, &age, age);
}

@end

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"vc:%p--%d", &age, age);
    age = 40;
    NSLog(@"vc:%p--%d", &age, age);
    [[Person new] add];
    NSLog(@"vc:%p--%d", &age, age);
    [Person reduce];
    NSLog(@"vc:%p--%d", &age, age);
    [[Person new] ds_add];
}

Copy the code

Print result: 2020-03-23 16:53:35.671470+0800 ThreadDemo[40300:1619888] VC: 0x103688CC0 — 1020-03-23 16:53:35.671611+0800 ThreadDemo[40300:1619888] vc: 0x103688CC0 –40 2020-03-23 16:53:35.671809+0800 ThreadDemo[40300:1619888] 0x60000239C640 > -0x103688D88 –11 2020-03-23 16:53:35.671926+0800 ThreadDemo[40300:1619888] vc: 0x103688CC0 –40 2020-03-23 Person internal: person-0x103688D88 –10 2020-03-23 16:53:35.672183+0800 ThreadDemo[40300:1619888] vc: 0x103688CC0 –40 2020-03-23 16:53:35.672332+0800 ThreadDemo[40300:1619888] Person (DS) Internal : -0x103688CC4 –11

As you can see from the above results, the values of the Person class, the Person class, and the Controller operations on the static variable age do not affect each other.

2. Memory management scheme

The OC provides the following memory optimization management schemes: Tagged Ponter, NONPOINTER_ISA, and SideTable. The following three schemes are explained one by one.

2.1, Tagged Ponter

In September 2013, apple launched iPhone5s, which was equipped with A7 dual-core processor with 64-bit architecture for the first time. In order to save memory and improve execution efficiency, apple proposed the concept of Tagged Pointer.

  • Tagged PointerIs specifically used to store small objects, such asNSNumber.NSDateAnd so on.
  • Tagged PointerThe value of a pointer is no longer an address, but a real value. So, it’s not really an object anymore, it’s just a normal variable in an object’s skin. So, its memory is not stored in the heap and is not neededmallocandfree.
  • Three times more efficient at memory reads and 106 times faster at creation.

So what is Tagged Ponter’s point of memory optimization?

2.1.1 Tagged Ponter Memory Optimization

For an NSNumber object, the value is an integer. Normally, if this integer is just a normal variable of NSInteger, it will account for 4 bytes on a 32-bit CPU and 8 bytes on a 64-bit CPU. The NSNumber object also has an ISA pointer, which is 4 bytes on a 32-bit CPU and 8 bytes on a 64-bit CPU. So moving from a 32-bit machine to a 64-bit machine doubles the memory footprint of objects like NSNumber and NSDate, even though the logic does not change. See the picture below (taken from Tang Qiao’s blog) :

In fact, the memory space required by the value of a variable such as NSNumber and NSDate usually does not need 8 bytes, so the waste of memory space for data storage as mentioned above is very large. Tagged Ponter solves this problem. Tagged Ponter splits an object pointer into two parts. One part saves data directly, and the other part is used as a special marker to indicate that this pointer is a special pointer that does not point to any address. So, after the Tagged Pointer object is introduced, the memory graph for the NSNumber on a 64-bit CPU looks like this:

2.1.2 Low-level exploration of Tagged Ponter

Take a look at Tagged Ponter’s underlying source code.

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
    if (tag <= OBJC_TAG_Last60BitPayload) {
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    } else {
        uintptr_t result =
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return_objc_encodeTaggedPointer(result); }}Copy the code

_objc_decodeTaggedPointer and _objc_taggedPointersEnabled encode and unencode pointer to taggedPointer. Both of these methods are xOR operations between the pointer address and objC_debug_taggedPOinter_obfuscator. As we all know, xor operations from A and B to C and xor operations from A can retrieve the value of A. Usually, this method can be used to achieve the exchange of two values without intermediate variables. Tagged Pointer uses this principle. Tagged Pointer is no longer an address, but a real value. What we need to know is how Tagged Pointer is stored. Look at the following code:

#define _OBJC_TAG_MASK (1UL << 63)
NSMutableString *mutableStr = [NSMutableString string];
NSString *immutable = nil;
char c = 'a';
do {
    [mutableStr appendFormat:@"%c", c++];
    immutable = [mutableStr copy];
    NSLog(@"0x%lx %@ %@", _objc_decodeTaggedPointer_(immutable), immutable, immutable.class);
} while (((uintptr_t)immutable & _OBJC_TAG_MASK) == _OBJC_TAG_MASK);
Copy the code

Print result: 2020-03-25 17:05:22.784213+0800 taggedPointer[76305:3706620] 0xA000000000000611 a NSTaggedPointerString 2020-03-25 17:05:22.784368+0800 taggedPointer[76305:3706620] 0xA000000000062612 AB NSTaggedPointerString 2020-03-25 17:05:22.784481+0800 taggedPointer[76305:3706620] 0xA000000006362613 ABC NSTaggedPointerString 2020-03-25 17:05:22.784594+0800 taggedPointer[76305:3706620] 0xA000000646362614 ABCD NSTaggedPointerString 2020-03-25 17:05:22.784698+0800 taggedPointer[76305:3706620] 0xA000065646362615 abcde NSTaggedPointerString 2020-03-25 17:05:22.784791+0800 taggedPointer[76305:3706620] 0xA006665646362616 abcDEF NSTaggedPointerString 2020-03-25 17:05:22.784874+0800 taggedPointer[76305:3706620] 0xA676665646362617 abcDefg NSTaggedPointerString 2020-03-25 17:05:22.784955+0800 taggedPointer[76305:3706620] 0xA0022038a0116958 abcDefgh NSTaggedPointerString 2020-03-25 17:05:22.785044+0800 taggedPointer[76305:3706620] 0xA0880E28045a5419 abcDefghi NSTaggedPointerString 2020-03-25 17:05:22.785173+0800 taggedPointer[76305:3706620] 0x409bac70e6d7a14B abcDefghij __NSCFString

As you can see from the code above, the string type output is __NSCFString when the string length increases to 10, and NSTaggedPointerString when the string length is less than 10, and the address starts with 0xA. Going back to the code above, the condition in the while is used to determine if the highest value in the 64-bit data is 1 to determine if the current object is a Tagged Pointer object. We convert 0xA to binary 1010, where the highest bit 1 indicates that the object is a Tagged Pointer object, and the remaining 010(decimal 2) indicates that the object is an NSString. Where is the value of the object? Take 0xA000000000000611, where 61 is the a in the corresponding ASII code. Others can be followed in the same way.

The following is a definition of the various flag bits provided by the system.

enum objc_tag_index_t : uint16_t
{
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 

    // 52-bit payloads
    OBJC_TAG_Photos_1          = 8,
    OBJC_TAG_Photos_2          = 9,
    OBJC_TAG_Photos_3          = 10,
    OBJC_TAG_Photos_4          = 11,
    OBJC_TAG_XPC_1             = 12,
    OBJC_TAG_XPC_2             = 13,
    OBJC_TAG_XPC_3             = 14,
    OBJC_TAG_XPC_4             = 15,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};
Copy the code

The system provides a method to check whether it is Tagged Pointer

# define _OBJC_TAG_MASK (1UL<<63)
static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
Copy the code

For more details, see Tagged Pointers

2.2, NONPOINTER_ISA

NONPOINTER_ISA is also apple’s solution for memory optimization. Storing a memory address in 64 bits is obviously a waste, since few devices have that much memory. You can then optimize your storage plan to use some of the extra space to store other content. The isa pointer with a first digit of 1 indicates that the optimized ISA pointer is used. The isa pointer structure is listed here in the 64-bit environment under __x86_64__ schema, which differs from __arm64__ schema.

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
      uintptr_t nonpointer        : 1;                                         
      uintptr_t has_assoc         : 1;                                         
      uintptr_t has_cxx_dtor      : 1;                                         
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ 
      uintptr_t magic             : 6;                                         
      uintptr_t weakly_referenced : 1;                                         
      uintptr_t deallocating      : 1;                                         
      uintptr_t has_sidetable_rc  : 1;                                         
      uintptr_t extra_rc          : 8
    };
#endif
};
Copy the code
  • nonpointer: indicates whether pointer optimization is enabled for ISA. 0 indicates a pure ISA pointer, and 1 indicates that in addition to the address, it contains some information about the class, the reference count of the object, and so on
  • has_assoc: Flag bit of the associated object, 0 does not exist, 1 exists.
  • has_cxx_dtor: does the object have a destructor in C++ or Objc? If it has a destructor, it needs to do some logical processing of the destructor. If not, it can release the object faster.
  • shiftclsIf pointer optimization is enabled, 33 bits in the ARM64 bit store the pointer to the class
  • magic: Determines whether the current object is a real object or an uninitialized space
  • weakly_referenced: Whether a weak variable is pointed to or has been pointed to an ARC, objects without weak references are released faster.
  • deallocating: indicates whether memory is being freed.
  • has_sidetable_rc: Whether there is an auxiliary reference count hash table. When the object reference technique is greater than 10, the variable is borrowed to store the carry.
  • extra_rc: represents the reference count value of this object, which is actually the reference count value minus 1. For example, if the object’s reference count is 10, extra_rc is 9. If the reference count is greater than 10, the following has_sideTABLE_rc is used.

Its structure is shown in the figure below:

2.3, SideTable

SideTable plays an important role in OC. In runtime, the object reference count and weak references are managed through SideTable. At the same time, the system maintains a global SideTables, which is a collection of SideTables.

Let’s look at the SideTable definition:

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 objectsThe 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: Hash table that stores weak reference Pointers to objects. Is the core data structure of the weak function in OC.

For more information on SideTable, please refer to my previous article on iOS underlying Principles: Weak implementation principles, which covered SideTable in detail.

3. Reference counting

3.1. What is reference counting

Reference counting is a memory management technique in computer programming languages. It refers to the process of saving the number of references to a resource (object, memory, disk space, etc.) and releasing it when the number of references reaches zero. Automatic resource management can be achieved using reference counting techniques. Reference counting can also refer to garbage collection algorithms that use reference counting techniques to reclaim unused resources.

When an object is created and allocated memory in the heap, the object reference count is 1; When other objects need to hold the object, the reference count of the object needs to be increased by one. When other objects no longer need to hold the object, the reference count of the object needs to be reduced by 1. When the object’s reference count reaches zero, the object’s memory is immediately freed and the object is destroyed.

  • callAlloc, new, copy, mutableCopyAn object created by a method beginning with the name, whose reference count is incremented by one.
  • callretainMethod increments the reference count of the object by one.
  • callreleaseMethod, the reference count of the object is reduced by 1.
  • autoreleaseMethod does not change the value of the object’s reference counter, but simply adds the object to the automatic release pool.
  • retainCountMethod returns the reference-count value of the object.

3.2 Object holding rules

Object holding rules are as follows:

  1. Self generated objects, own.
  2. Objects that are not generated by themselves can be held by themselves.
  3. Release when you no longer need to hold objects yourself.
  4. Objects that are not owned by you cannot be released.

The criterion for holding an object is the value of the reference count of the object. Then, combining the method of object creation, the reference count of the object is added or subtracted and the destruction of the object is roughly as follows:

The object operation Objective – C method
Generate and hold objects Alloc/new/copy/mutableCopy method
Hold the object Retain method
Release object Release method
Discarded objects Dealloc method

3.2.1. The objects generated by themselves shall be held by themselves

Method names that begin with the following names mean that the generated object is owned only by itself: alloc, new, copy, and mutableCopy. Object creation in OC can be done by both alloc and new.

NSObject *obj = [NSObject alloc]; NSObject *obj1 = [NSObject new]; // equivalent to NSObject *obj1 = [[NSObject alloc]init];Copy the code

For more information about alloc and new, please refer to the previous article on the underlying principles of IOS alloc, Init and new, which will not be described here. Let’s focus on copy and mutableCopy, which means a copy of an object. Object copy must comply with the NSCopying and NSMutableCopying protocols.

@interface Person : NSObject<NSCopying,NSMutableCopying>

@end

@implementation Person


- (nonnull id)copyWithZone:(nullable NSZone *)zone { 
    Person *person = [[self class] allocWithZone:zone];
    return person;
}

- (id)mutableCopyWithZone:(NSZone *)zone{
    Person *person = [[self class] allocWithZone:zone];
    return person;
}

@end

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc]init];
    Person *person1 = [person copy];
    Person *person2 = [person mutableCopy];
    NSLog(@"person:%p--person1:%p--person2:%p",person,person1,person2);
}
Copy the code

2020-03-26 15:56:26.666859+0800 taggedPointer[89806:4395707] Person: 0x6000038342C0 retainCount: 1 2020-03-26 15:56:26.667011+0800 taggedPointer[89806:4395707] person1:0x6000038342f0 retainCount: 1 2020-03-26 15:56:26.667113+0800 taggedPointer[89806:4395707] person2:0x600003834300 retainCount: 1

Person1, Person2, person2, person2, person2, person1, person2, person2, person2 And they all have a reference count of 1. The difference between copy and mutableCopy is that the former produces objects that cannot be changed, while the latter produces objects that can be changed.

It should be noted that the alloc method does not operate on retainCount, and the reference count is 1 because the underlying retainCount method defaults to +1.

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

Copy the code

3.2.1.1 shallow copy and deep copy

Now that we’ve talked about copy and mutableCopy, let’s talk about shallow and deep copies.

Shallow copy: A pointer copy of an object that does not create new memory. Deep copy: Copying the object itself creates a new object that points to a different memory address.

There are some differences between copy and mutableCopy for immutable objects (such as NSString, NSArray, NSDictionary) and mutable objects (such as NSMutableString, NSMutableArray, NSMutableDictionary) , as shown in the following table:

For mutable objects of a collection class, deep copy is not strictly deep copy. Although new memory is created, it is still shallow copy for elements stored in an array.

3.2.2 Objects that are not generated by themselves can be held by themselves

An object obtained by methods other than alloc, new, copy, or mutableCopy is not the owner of the object because it is not produced and held by itself.

Id obj = [NSMutableArray array]; // Hold object by retain [obj retain];Copy the code

In the code above, NSMutableArray generates an object through the class method array and assigns it to the variable OBj, but the variable obj does not hold the object itself. Objects can be held using the retain method.

3.2.3 Release the objects they hold when they are no longer needed

When an object is no longer needed, the owner is obligated to call release to release the object. Of course, in the ARC environment, the developer does not need to actively call the method. The system will automatically call the method, but in the MRC environment, the developer needs to manually call the retain and release methods of the object in the appropriate place.

3.2.4 Objects not held by oneself cannot be released

Objects generated and held by methods alloc, new, Copy, mutableCopy, or retained by the retain method need to be released when they are no longer needed because the owner is the object. Objects obtained beyond this must not be released. A crash can occur if an object is released in a program that it does not own.

Obj = [[NSObject alloc] init]; // release object [obj release]; [obj release];Copy the code

Releasing objects that you don’t own will definitely cause your application to crash. So never release objects that you don’t own.

Alloc, retain, release, dealloc, autorelease implementation

3.3.1 Alloc implementation

The bottom line is that alloc creates the object and claims a chunk of memory no less than 16 bytes. For the implementation of alloc, please refer to the previous article on the underlying principles of IOS alloc, init, and new, which will not be described here.

3.3.2 Retain implementation

In the previous section, the extra_RC field in BITS of ISA and the RefcountMap Refcnts in SideTable both have store reference counts. What is the connection between the two? Let’s look at the reference count storage using the source code for RETAIN.

id objc_retain(id obj)
{
    if(! obj)return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
Copy the code

The first is the objc_retain method, which has an internal determinant to determine whether the current object is TaggedPointer or not. If so, it returns. Otherwise, the retain method is called. We can also see from this that the TaggedPointer object does not do reference counting.

inline id objc_object::retain() { assert(! isTaggedPointer());if(fastpath(! ISA()->hasCustomRR())) {return rootRetain();
    }

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

Inside the retain method is simply a judgment and a call to the rootRetain method. “Fastpath” means high probability of occurrence.

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    // Return TaggedPointer directly
    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 ISA is not optimized by NONPOINTER_ISA
        if(slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits);if(! tryRetain && sideTableLocked) sidetable_unlock();if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();// Reference counts are stored in SideTable
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        // Check that objects are being destructed
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if(! tryRetain && sideTableLocked) sidetable_unlock();return nil;
        }
        uintptr_t carry;
        // the extra_rc in bits of isa adds 1
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        // If bits extra_rc is full, store half of it in sideTable
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if(! handleOverflow) { ClearExclusive(&isa.bits);return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if(! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked =true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;// the value extra_rc is half-empty
            newisa.has_sidetable_rc = true; }}while(slowpath(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        // Store the other half of the reference count in sideTable
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if(slowpath(! tryRetain && sideTableLocked)) sidetable_unlock();return (id)this;
}

Copy the code

The rootRetain method is the core method for the retain reference count. We can see that the method does the following:

  1. Checks whether the current object is oneTaggedPointerIf so, return.
  2. judgeisaWhether it wasNONPOINTER_ISAOptimize, if not, store the reference count inSideTableIn the. 64-bit devices will not enter this branch.
  3. Determines whether the current device is being destructed.
  4. willisathebitsIn theextra_rcAdd one.
  5. If theextra_rcIs already fullsidetable_addExtraRC_nolockMethod moves half of the reference count toSideTableIn the.

3.3.3 release implementation

In the previous chapter, we looked at bits and SideTable for reference counts. How does release subtract one from a reference count?

void 
objc_release(id obj)
{
    if(! obj)return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}
Copy the code

The first is the objc_Release method. Inside that method there is a method that determines whether the current object is TaggedPointer or not, and returns if it is, otherwise release is called.

inline void
objc_object::release() { assert(! isTaggedPointer());if(fastpath(! ISA()->hasCustomRR())) { rootRelease();return;
    }

    ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}

Copy the code

Inside the release method, it’s very simple, it’s just a judgment, and then it calls rootRelease. “Fastpath” means high probability of occurrence.

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // Check if it is TaggedPointer
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // If ISA is not NONPOINTER_ISA optimized, the reference count in SideTable is cleaned up
        if(slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits);if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        // the bits extra_rc of isa subtracts 1
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        // the extra_rc is empty
        if (slowpath(carry)) {
            // don't ClearExclusive()
            gotounderflow; }}while(slowpath(! StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)));if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;
    // has_sidetable_rc of ISA indicates whether there is an auxiliary reference-counting hash table
    if (slowpath(newisa.has_sidetable_rc)) {
        if(! handleUnderflow) { ClearExclusive(&isa.bits);return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if(! sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked =true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.
        //
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if(! stored) {// Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                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) {// Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.}}// Really deallocate.

    if (slowpath(newisa.deallocating)) {
        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;

    if (slowpath(sideTableLocked)) sidetable_unlock();

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

The rootRelease method is the core method of the release reference count. We can see that the method does the following:

  1. Checks whether the current object is oneTaggedPointerIf so, return.
  2. judgeisaWhether it wasNONPOINTER_ISAOptimize, if not optimized, then clear inSideTableReference count in. 64-bit devices will not enter this branch.
  3. willisathebitsIn theextra_rcLet’s subtract 1.
  4. ifextra_rcIf it is empty, it is clearedSideTableReference count in.
  5. Try toSideTableThe reference count is moved toisathebitIn the.

3.3.4 Autorelease implementation

You can’t talk about Objective-C memory management without mentioning AutoRelease. As the name implies, an autorelease is an automatic release. This looks a lot like ARC, but is actually more similar to the C language feature of automatic variables (local variables). Autorelease treats object instances like local variables in C. When it goes out of scope, the release instance method of the object instance is called. In addition, unlike local variables in C, programmers can set the scope of variables. Autorelease can be used as follows:

  • Generate and holdNSAutoreleasePoolObject.
  • Calling the allocated objectautoreleaseInstance method.
  • abandonedNSAutoreleasePoolObject.

Look at the code implementation of autoRelease.

id objc_autorelease(id obj)
{
    if(! obj)return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}
Copy the code

The first is the objc_AutoRelease method. Inside this method there is a method that determines whether the current object is TaggedPointer or not, and returns if it is, otherwise autoRelease is called.

inline id 
objc_object::autorelease()
{
    if (isTaggedPointer()) return (id)this;
    if(fastpath(! ISA()->hasCustomRR()))return rootAutorelease();

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

The autoRelease method internally again determines whether the current object is TaggedPointer and returns if it is, otherwise rootAutorelease is called. “Fastpath” means high probability of occurrence.

inline id objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

id objc_object::rootAutorelease2() { assert(! isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}
Copy the code

At the core of the rootAutorelease code is to add the current object to the AutoreleasePool automatic release pool.

3.3.5 Dealloc object destruction

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()
{
    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
  1. First determine whether the object isTagged PointerIf yes, return directly.
  2. 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_assocThere is no custom C++ destructor! isa.has_cxx_dtorSideTable is not used for reference counting! isa.has_sidetable_rcIs directly released quickly.
  3. If the condition in 2 is not met, it is calledobject_disposeMethods.

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 map of the AssociationManager. Call the clearDeallocating method to remove the associated reference to the object.

inline void 
objc_object::clearDeallocating()
{
    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 to determine whether the object is optimized for ISA reference counting, and if it isn’t, it needs to clean up the reference count data stored in SideTable. Isa.has_sidetable_rc) or weak (ISa.Weakly_referenced). Weak (ISa.weakly_referenced) Call the clearDeallocating_slow method.

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    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

The clearDeallocating_slow method has two branches. First, if the object is weakly referenced, the weak_clear_NO_lock method is called in the SideTable weak_table to clean up this. Remove this from SideTable’s reference count if SideTable is used for reference counting.

3.4 Rules under ARC

  1. Retain, release, retainCount, and AutoRelease cannot be explicitly called.
  2. NSAllocateObject and NSDeallocateObject cannot be used.
  3. Memory management naming conventions must be followed.
  4. Do not explicitly call dealloc.
  5. Use @autoreleasepool instead of NSAutoreleasePool.
  6. An NSZone cannot be used.
  7. Object variables cannot be members of C language constructs.
  8. Explicitly convert “id” and “void *”.

4, summarize

  1. The IOS memory layout area is roughly divided into five areas:Stack area, heap area, BSS section, data section, code section.
  2. In OC, memory optimization management schemes are as follows:Tagged Ponter, NONPOINTER_ISA, SideTable.
  3. Tagged PointerIs specifically used to store small objects, such asNSNumber, NSDateAnd so on. The value of its pointer is no longer an address, but a real value. So, its memory is not stored in the heap and is not neededmallocandfree. Three times more efficient at memory reads and 106 times faster at creation.
  4. NONPOINTER_ISAUse some of the extra space to store other content, which increases memory utilization.
  5. SideTableIs a hash table structure that performs operations on reference counts and weak reference tables.
  6. Object holding rules: the generated objects, their own hold; Objects that are not generated by themselves can be held by themselves; Release when you no longer need the object you hold. Objects that are not owned by you cannot be released.
  7. alloc/new/copy/mutableCopyMethods like generate and hold objects,retainMethod reference count increment by 1,releaseMethod reference count minus 1,deallocMethod destroys the object.
  8. autoreleaseMethod does not change the value of the object’s reference counter, but simply adds the object to the automatic release pool.
  9. ARC cannot be explicitly calledRetain, Release, retainCount, autoRelease.

The resources

  • Objective-c Advanced Programming (a good book) (Password :9bdl)
  • IOS: Alloc, Init and new
  • Underlying principle of iOS: weak implementation principle
  • Tagged Pointers
  • Objective-c Runtime mechanism (5) — iOS memory management
  • Understand Tagged Pointer