Memory management is an important concept in any language, and to write memory-efficient and bug-free code in a language, you need to know the ins and outs of its memory management model. This article covers memory management of OBJC under ARC in detail

Reference counting

It is well known that Objective-C uses reference counting to manage memory. We can use the example of turning on and off the lights in a room to illustrate the mechanism of reference counting. Suppose there is only one lighting device in the office. People entering the office at work need lighting, while people leaving the office after work do not need lighting. To solve the problem of turning on the light when there are people in the office and keeping it off when there are no people, we need to:

  • The first person to enter the office turns on the light
  • The person who enters the office after that needs lighting
  • People who leave the office don’t need lighting
  • The last person to leave the office turns off the lights (no one needs lighting)

In Objective-C, objects are the equivalent of office lighting. Use alloc/new/copy/mutableCopy method to create objects and equivalent to turn on the light and lighting, retain method hold object is equivalent to need lighting, release method to release the object is equivalent to not need lighting, dealloc method discarded object is equivalent to turn off the lights. Using the room light on and off example, we can also summarize the principles of Objc reference counting:

  • Self generated objects, own.
  • Objects that are not generated by themselves can be held by themselves.
  • Objects that no longer need to be held can be released by themselves.
  • Objects that are not owned by you cannot be released.

The data structure

SideTables

Apple uses a hash scheme to manage reference counts and weak Pointers for objects, implemented by the SideTables data structure. Its definition is as follows:

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
Copy the code

SideTables, even though it has an “S” after its name, is actually a global Hash table, and the contents inside it are all SideTable structures. Why not a single Side Table instead of a single Side Tables() data structure?

If there is only one Side Table, it means that the reference count or weak references of all objects allocated in memory are put into a big Table. At this time, when we need to operate on different objects in different threads, we need to keep locking the whole Side Table, which obviously leads to efficiency problems. In order to solve this problem, the system introduces the technology of separate lock.

  • Separate lock: can the memory object of reference counting table split into multiple parts, hypothesis into eight, need of eight table lock respectively, if the object A in table 1, the object B in table 2, when A and B for reference counting operation at the same time, can operate concurrently, but if only one table can only operate in order to separate lock can improve the efficiency of access.

How to use an object pointer to quickly locate which Side Table it belongs to? SideTables is essentially a Hash table, and in that Hash table, there might be 64 specific sides Table,SideTables can use the memory address of the object as the key, and calculate the corresponding SideTable by using a hash function operation (the memory address operation and the number of SideTables mod). Looking up the SideTable through a hash table compared to looking up objects through traversal provides efficiency. At the same time, since the object memory address is evenly distributed, when we use the hash function to get the corresponding SideTable, we can effectively reduce hash conflicts.

SideTable

SideTable contains three members: spin lock, reference count table, and weak reference table. Its definition is as follows:

Struct SideTable {// Spinlock_t slock; // Hash table RefcountMap refcnts; Weak_table_t weak_table; // Weak references the global hash table weak_table_t weak_table; }Copy the code

An spin lock

  • It is a “busy waiting” lock. If the current lock has been acquired by another thread, the current thread will continuously detect whether the lock is released. If it is released, the thread will acquire the lock first
  • Spin-locks are suitable for light access situations where the lock user holds the lock for a short time.

Reference the counter RefcountMap

A reference counter, as its name implies, is used to store reference counts, with data defined as follows:

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
Copy the code

The three parameters represent the hash key of the object, reference count, and whether to automatically release the corresponding hash node when the reference count is 0. The default value is true. The stored reference counts are sizt_T structures, with each bit representing the following meanings:

  • The first binary bit (WEAKLY_REFERENCED) indicates whether the object has weak references, and if so (value 1) all weak references to it need to be nil(equivalent to NULL in other languages) when the object is released to avoid wild pointer errors.
  • The second (DEALLOCATING) indicates whether the object is dealloc
  • The other bits store the actual number of references to the object, so adding or subtracting one in the reference COUNT is actually adding or subtracting four for the entire unsigned long

Weak reference table Weak_table_t

A weak application table is used to hold a weak reference pointer to a specified object. It is defined as follows:

Weak_entry_t * Weak_entries; struct weak_table_t {// Weak_entry_t *weak_entries; // Storage space size_t num_entries; // Uintptr_t mask; // Hash key maximum offset value uintptr_t max_hash_displacement; };Copy the code

Weak_entry_t indicates that weak_table_t is actually a hash table, where Weak_entries are a hash array that gives an object pointer as key. Through the hash function, the relevant information about weak_entry_t can be calculated to store weak_reference objects. So let’s look at weak_entry_t

weak_entry_t

Weak_entry_t stores weak reference information of an object, which is also a structure. Similar to weak_table_t, which stores a hash table Weak_referrer_t, weak reference to the “object pointer” pointer. By the operation of Weak_referrer_t, the weak reference pointer of the object can be set to nil after the object is released. Its definition is as follows:

struct weak_entry_t { DisguisedPtr<objc_object> referent; union { struct { weak_referrer_t *referrers; uintptr_t out_of_line : 1; uintptr_t num_refs : PTR_MINUS_1; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { // out_of_line=0 is LSB of one of these (don't care which) weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];  }; }Copy the code

It mainly consists of three parts:

  • Referent: The address of the object being referred to. The previous iteration of the search is to determine whether the target address is the same as it.
  • Referrers: Mutable array that holds the addresses of all weak references to this object. When the object is released, all the Pointers in the referrers are set to nil.
  • Inline_referrers is a four-element array that is used by default to store Pointers to weak references. Referrers are used to store Pointers when there are more than four.

Realize the principle of

After the introduction of data structure, I think you must be more confused, reference count table can understand, what is weak reference? Let’s go back to the reference-counting principle we first introduced. Mentioned generated inside, hold, release, waste objects, such as concept, and its corresponding alloc/retain/release/retainCount/dealloc method system. This section focuses on the internal implementation of these systems approaches.

alloc

After a series of function encapsulation and invocation, C function calloc was finally called. For details, please refer to ARC(above).

retain

The implementation of retain can be seen in the following code:

id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA assert(! isa.indexed); # endif / / by the current object pointer to this, the SideTables, after hash algorithm, to obtain it's SideTable SideTable * table = SideTable: : tableForPointer (this); // If (spinlock_trylock(&table->slock)) {// If (spinlock_trylock(&table->slock)) {// If (spinlock_trylock(&table->slock)) {// If (spinlock_trylock(&table->slock)) {// RefcntStorage (unsigned long) size_t& refcntStorage = table->refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {// then add +1 to the reference count value refcntStorage += SIDE_TABLE_RC_ONE; } spinlock_unlock(&table->slock); return (id)this; } return sidetable_retain_slow(table); }Copy the code
  • After two hash searches, the system finds the SideTable and then the size_t reference count
  • Since the first two bits of size_t do not store reference counts, the +1 operation is actually a +4 offset via the macro

release

Release is a bit more complicated than retain is that it needs to determine whether dealloc should eventually be called.

bool objc_object::sidetable_release(bool performDealloc) { #if SUPPORT_NONPOINTER_ISA assert(! isa.indexed); # endif / / by the current object pointer to this, the SideTables, after hash algorithm, to obtain it's SideTable SideTable * table = SideTable: : tableForPointer (this); Bool do_dealLOc = false; if (spinlock_trylock(&table->slock)) { RefcountMap::iterator it = table->refcnts.find(this); If (it == table-> refcnt.end ()) {// if (it == table-> refcnt.end ()) { table->refcnts[this] = SIDE_TABLE_DEALLOCATING; } else if (it->second < SIDE_TABLE_DEALLOCATING) {// Less than SIDE_TABLE_DEALLOCATING, the mark is 0 and dealloc do_dealloc = true; it->second |= SIDE_TABLE_DEALLOCATING; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {// reference count minus 1 IT ->second -= SIDE_TABLE_RC_ONE; } spinlock_unlock(&table->slock); if (do_dealloc && performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return do_dealloc; } return sidetable_release_slow(table, performDealloc); }Copy the code
  • After two hash searches, the system finds the SideTable and then the size_t reference count
  • If the object cannot be found, mark it as needing dealloc
  • If it is smaller than SIDE_TABLE_DEALLOCATING, the number of references is 0 and dealloc is required
  • Otherwise, the reference count is reduced by one, and the offset is actually done by macro
  • If you need dealloc, call dealloc

retainCount

uintptr_t objc_object::sidetable_retainCount() { SideTable *table = SideTable::tableForPointer(this); size_t refcnt_result = 1; spinlock_lock(&table->slock); 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; } spinlock_unlock(&table->slock); return refcnt_result; }Copy the code

An object that has just been alloc, in the reference count table, does not have a key-value mapping associated with the object, and this value reads it -> Second should be 0, and since the local variable refCNT_result is 1, the alloc only object that calls its retainCount gets 1

dealloc

NSObject executes dealloc by calling _objc_rootDealloc, object_Dispose, and then objc_destructInstance. The first steps are conditional checks and simple jumps, and the last function is as follows:

void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = ! UseGC && obj->hasAssociatedObjects(); bool dealloc = ! UseGC; // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); if (dealloc) obj->clearDeallocating(); } return obj; }Copy the code
  • Is there any C++ related content, or is the current object using ARC(hasCxxDtor)? If so, call object_cxxDestruct()
  • Determine if there are associated objects, and if so, call _object_remove_assocations()
  • Call clearDeallocating() to clean up the weak reference.

Automatic reference counting ARC

Automatic Reference Counting refers to the Automatic Reference Counting in memory management and the corresponding Manual Reference Counting (MRC). While MRC is rarely used by OC developers these days, a significant portion of ARC is made up of the mechanics and principles of MRC.

Use rules

In ARC, the compiler takes care of memory management and the developer does not have to type retain or release again. But follow these rules:

  • Cannot use retain/release/retainCount/autorelease.
  • Cannot use the NSAllocateObject/NSDeallocateObject.
  • Memory management naming conventions must be followed.
  • Undisplayable calls to dealloc, such as [super dealloc].
  • Use @autorelease instead of NSAutoreleasePool.
  • NSZone cannot be used.
  • Object type variables cannot be C struct members (can be used after the __unsafe_unretain modifier)
  • The translation ID and void* cannot be displayed (_bridge,_bridge_retained,__bridge_transfer).
  • The keywords of weak and strong properties are added

A circular reference

Here again, we refer to weak references. So what is a weak reference, and what does it do? How did he do that? Before we get to weak references, let’s take a quick look at the strong modifier. It is the default ownership modifier for id types and object types. Represents a strong reference to an object. Variables that hold strong references are discarded when they go out of scope, and as the strong references expire, the referenced objects are released. The strong modifier seems perfect for memory management. Unfortunately, reference counting memory management inevitably causes circular reference problems. Here’s an example:

@interface Test: NSObject @property(nonatomic, strong) object; @implementation Test - (id) init { ..... } @end {// loop reference id test0 = [[Test alloc] init]; id test1 = [[Test alloc ] init]; [test0 setObject:test1]; [test1 setObject:test0]; }Copy the code

If obj of object test0 points to object test1 and obj of object test1 points to object test0, it is a circular reference. Circular references are generated because test0 and test1 objects do not have a reference count of 0. Therefore, the object cannot be released. A memory leak has occurred.

In addition, there are two cases of circular references:

  • Self-circular reference: Suppose we have an object that internally holds its member variable obj, and we assign obj to the original object
  • Multi-loop reference: If there is object 1 in the class… Object N, each object holds a strong OBJ, and if each object’s OBJ points to the next object, a multi-loop reference is generated

So how do you break circular references? There are usually two ways to break circular references

  • Use weak references to avoid circular references
  • Manually break the loop when appropriate

A weak reference, as the name suggests, is the opposite of a strong reference. Weak references do not hold objects, so when a variable is out of scope, the object is released. The other thing about weak references is that when an object is deprecated, weak references are automatically set to nil.

A weak reference

So how are weak references implemented? Let’s start with the following code:

NSObject *obj = [[NSObject alloc] init];
__weak NSObject *obj1 = obj;
Copy the code

After conversion by the compiler, it becomes the following implementation:

id obj1;
objc_initWeak(&obj1,obj); 
Copy the code

Let’s look at the implementation of objc_initWeak:

Id objc_initWeak(id *location, id newObj) {// Check whether the object instance is valid // If (! newObj) { *location = nil; return nil; Return storeWeak<false/*old*/, true/*new*/, true/*crash*/> (location, (objc_object*)newObj); }Copy the code

As you can see, the function first makes some exception judgments and then calls the storeWeak function. The code is as follows:

// HaveOld: true - variable has value // false - need to be cleaned up in time, current value may be nil // HaveNew: True - New value to be assigned, the current value might be nil // false - no new value to be assigned // CrashIfDeallocating: True - newObj has been freed or newObj does not support weak references. <bool HaveOld, bool HaveNew, bool CrashIfDeallocating> static id storeWeak(id *location, Objc_object * newObj) {/ / the procedure used to update a weak reference pointer pointing to / / initialization previouslyInitializedClass pointer Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; // Use the address to index the bucket to prevent it from repeating. // Use the following operations to change the old value retry: If (HaveOld) {// change pointer to oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (HaveNew) {newTable = &SideTables()[newObj]; } else { newTable = nil; SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable); If (HaveOld && *location!) {// If (HaveOld && *location! = oldObj) { SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); goto retry; } // prevent deadlock between weak references // and ensure that the isa of all weak references is non-null to if (HaveNew && newObj) {// get the ISA pointer to the new object Class CLS = newObj->getIsa(); If (CLS! = previouslyInitializedClass && ! (((objc_class *) CLS)->isInitialized()) {SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(CLS, (id)newObj)); // If +initialize is in a thread // for example, +initialize is calling storeWeak and needs to manually add a protection policy to it. Mark previouslyInitializedClass = CLS and set previouslyInitializedClass pointer; // Retry goto; If (HaveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); If (HaveNew) {newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating); Weak_register_no_lock method returns nil if the weak reference is released // Set the if reference flag bit in the reference count table if (newobj&&! NewObj ->setWeaklyReferenced_nolock(); weakLyReferenced_nolock (); weakLyReferenced_nolock (); *location = (id)newObj; *location = (id)newObj; SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); return (id)newObj; }Copy the code
  • Does haveOld haveOld values? For example, the weak pointer obj1 points to another object, test, before pointing to objc
  • If haveNew has a new value, weak pointer is pointed to objc
  • According to the above two flag bits, the old and new sidetables are obtained
  • Make sure the new object is initialized
  • If there are old values, clear the weak pointer binding of the old value using weak_unregister_no_lock
  • If there is a new value, bind the new weak pointer using weak_register_no_lock

Weak_unregister_no_lock is implemented as follows:

#define WEAK_INLINE_COUNT 4 void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, Id *referrer_id) {// In the entry method, weak_table weak reference table is passed, OldObj and *location objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; weak_entry_t *entry; // If its object is nil, there is no need to unregister if (! referent) return; Weak_entry_for_referent () weak_entry if ((entry = weak_entry_for_referent(weak_table,)) Referent))) {remove_referrer(entry, referrer); bool empty = true; If (entry->out_of_line && entry->num_refs! = 0) { empty = false; } else {// Null for (size_t I = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i]) { empty = false; break; Weak_entry_remove (weak_table, entry); weak_entry_remove(weak_table, entry); weak_entry_remove(weak_table, entry); } // Referrer = nil is not set here, because objc_storeWeak() will need this pointer}Copy the code
  • The corresponding weak_entry_t can be found through oldObj and weakTable.
  • Weak pointer address pass is used to disassociate weak references
  • If weak_entry_t is empty after removal, remove the corresponding entry

Weak_register_no_lock binds the weak pointer to the new object as follows: weak_register_no_lock: weak_register_no_lock: weak_register_no_lock: Weak_register_no_lock: Weak_register_no_lock

id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, Bool crashIfDeallocating) {// In the entry method, weak_table is passed in, OldObj and *location objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; // Check whether the object is valid and tagged Pointer technology is used. referent || referent->isTaggedPointer()) return referent_id; // The hasCustomRR method checks whether the class (including its parent) contains the default method bool DealLocating; if (! Referent ->ISA()->hasCustomRR()) {// Check the dealloc state DealLocating = Referent ->rootIsDeallocating(); } else {// referent SEL_allowsWeakReference: 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); } // Crash is caused by dealloc, 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 { return nil; Weak_entry Weak_entry_t *entry;} // Record and store the corresponding reference table. Weak_table if ((entry = weak_entry_for_referent(Weak_table, Append_referrer (entry, referrer)) {// add the weak reference table to the attached object. } else {// create weak_entry_t new_entry; new_entry.referent = referent; new_entry.out_of_line = 0; new_entry.inline_referrers[0] = referrer; for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) { new_entry.inline_referrers[i] = nil; Weak_grow_maybe (weak_table); weak_grow_maybe(weak_table); weak_grow_maybe(weak_table); Weak_entry_insert (Weak_table, &new_entry); weak_entry_insert(weak_table, &new_entry); weak_entry_insert(weak_table, &new_entry); } // Referrer = nil is not set here, because objc_storeWeak() will require this pointer to return referent_id; }Copy the code

What happens to the weak variable when an object is discarded/released? In dealloc’s implementation, if there is a weak reference, the weak_clear_no_lock() function is called in clearDeallocating() to remove the weak reference. Its implementation is as follows:

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; /** Through the newly declared local variable referent, weak_table in the weak reference table to find its corresponding weak reference array entry. Weak_entry_for_referent function: it was also encountered when adding the weak variable. Through the pointer of the discarded object, the index position of the weak reference array is calculated by hashing algorithm. Returns an array of weak references to the current object to the caller through the index. */ weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); 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; /** If the number of weak reference variables is less than 4, inline_referrers is selected, and vice versa. 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) {// *referrer: all weak references to the current object that have been modified = referrers[I]; If (*referrer == nil) {if (*referrer == nil) {if (*referrer == nil; } else if (*referrer) { _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); }Copy the code

Weak_clear_no_lock will find the weak reference table according to the current object pointer, take out the weak references corresponding to the current object, which is an array, and then traverse the number group, traverse all the weak reference Pointers, if the address represented by the weak reference pointer is the abandoned address referent, it is set to nil.

Automatic release tank

Finally, let’s look at some code:

- (void)viewDidLoad {
	[super viewDidLoad];
    NSString *str = [NSString stringWithFormat:@"autorelease"];
    NSLog(@"%@",str);   //Console: autorelease
Copy the code

The Console output is STR, so the question is, when is STR released? Is it when viewDidLoad is called out of scope? The answer is that at the end of each runloop, the AutoreleasePool created previously is popped and a new AutoreleasePool is pushed in, so the STR object created in viewDidLoad is the STR object created when the next runloop will When the AutoreleasePoolPage pop method is called, the Autorelease object can also be released manually:

- (void)viewDidLoad {
	[super viewDidLoad];
    @autoreleasepool {
    	NSString *str = [NSString stringWithFormat:@"autorelease"];
    }
    NSLog(@"%@",str);   //Console: null
Copy the code

In ARC, we use @autoreleasepool{} to use an Autoreleasepool, which the compiler then overwrites to look like this:

void *context = objc_autoreleasePoolPush();
NSString *str = [NSString stringWithFormat:@"autorelease"];
objc_autoreleasePoolPop(context);
Copy the code

Objc_autoreleasePoolPush () and objc_autoreleasePoolPop are implemented as follows:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code

AutoreleasePoolPage push and pop are used to implement both functions.

class AutoreleasePoolPage { magic_t const magic; id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; id * begin() { return (id *) ((uint8_t *)this+sizeof(*this)); } id * end() { return (id *) ((uint8_t *)this+SIZE); } bool empty() { return next == begin(); } bool full() { return next == end(); } bool lessThanHalfFull() { return (next - begin() < (end() - begin()) / 2); }... }Copy the code

As can be seen:

  • AutoreleasePoolPage objects are connected together in a bidirectional linked list, with parent pointing to the parent and child pointing to the child
  • Next points to the next location of the newly added Autoreleased object, initialized to begin();
  • Thread refers to the current thread. AutoreleasePoolPage and thread one – to – one correspondence.

Internal implementation of the Push method in AutoreleasePoolPage

  • Whenever an objc_autoreleasePoolPush call is made, the Runtime adds a POOL_BOUNDARY to the current AutoreleasePoolPage with a value of 0 (nil).
  • By pointing the next pointer to the next stackable location, you are essentially inserting sentinels into the stack each time a block of AutoreleasePool code is created

Obj AutoRelease method implementation

When we need to push an AutoRelease object, we first determine whether the current next pointer points to the top of the stack. If not, we add the object directly to the next position in the current stack. If next is already at the top of the stack, there is no way to add a new AutoRelease object to the current AutoreleasePoolPage and an AutoreleasePoolPage object will be created. The child of the first AutoreleasePoolPage object points to the second AutoreleasePoolPage object and the parent of the second AutoreleasePoolPage object points to the first AutoreleasePoolPage object. As shown below:

The internal implementation of the Pop method in AutoreleasePoolPage, objc_autoreleasePoolPush, returns the address of the sentry object, which is taken as an input by objc_autoreleasePoolPop

  • Find the page of the sentinel object based on the address of the sentinel object passed in
  • In the current page, send a -release message to all AutoRelease objects inserted after the sentinel object and move the next pointer back to the correct position
  • Clean up from the most recently added object, spanning several pages to the page where the sentinel is

The multiple nested calls to Autoreleasepool insert the sentinel object multiple times. Each time we create an Autoreleasepool code block, the system inserts the sentinel object for us

Back to the original question, when was STR released? IOS registers two observers in the Runloop of the main thread

  • The first Observer listens for the kCFRunLoopEntry event and calls objc_autoreleasePoolPush().
  • A second Observer
    • Objc_autoreleasePoolPop (), objc_autoreleasePoolPush()
    • Objc_autoreleasePoolPop () is called after listening for the kCFRunLoopBeforeExit event

Advanced programming in Objective-C -ios and OS X Multithreading and memory management in a simple way ARC(top) black screen behind the Autorelease weak weak reference implementation