What is an automatic release pool

AutoreleasePool is an automatic memory collection mechanism in OC that delays the release of variables added to the AutoreleasePool. In simple terms, when an object is created, the variable is normally released immediately when it is out of scope. If an object is added to the autoreleasepool pool, it is not immediately released until the runloop is dormant/out of the Autoreleasepool scope {}

Automatically releases the lifetime of the pool

  1. From the time the program starts until the load is complete, the runloop corresponding to the main thread is dormant, waiting for user interaction to wake up the Runloop
  2. Each user interaction launches a Runloop that handles all of the user’s clicks, touches, and so on
  3. When the Runloop listens to an interaction event, it creates an automatic release pool and adds all delayed release objects to the automatic release pool
  4. Before a complete runloop ends, a release message is sent to all objects in the automatic release pool, and then the automatic release pool is destroyed

Automatically release the pool’s data structures

Class AutoreleasePoolPage {//magic Checks whether the structure of AutoreleasePoolPage is complete magic_t const magic; // 16 bytes // points to the next location of the newly added autoreleased object, initialized to begin(); id *next; Pthread_t const thread; //parent points to the parent node, the parent of the first node points to nil; AutoreleasePoolPage * const parent; // 8 bytes // Child points to the child node, the first node child points to nil; AutoreleasePoolPage *child; // 8 bytes // uint32_t const depth; //hiwat stands for high water mark; uint32_t hiwat; // 4 bytes... }Copy the code

AutorealeasePool is a two-way linked list of AutoreleasePoolPage

AutoreleasePoolPageIs a bidirectional linked list node, singleAutoreleasePoolPageThe structure is as follows:

56 bits are used to store member variables of AutoreleasePoolPage, and the remaining 0x100816038 to 0x100817000 are used to store objects added to the AutoreleasePoolPage pool. Each AutoreleasePoolPage consists of a series of autoreleasepoolPages, each of which has a size of 4096 bytes (hexadecimal 0x1000).

1: The instance methods of the begin() and end() classes help us quickly get the boundary addresses from 0x100816038 to 0x100817000.

2: Next points to the next empty memory address. If an object is added to the address next points to, it will move to the next empty memory address as shown in the figure below.

The automatic release pool is essentially a pointer stack. Each pointer is either to the object to be released or POOL_BOUNDARY(sentinel object).

The token is essentially a pointer to the POOL_BOUNDARY and stores the address of the POOL_BOUNDARY inserted during each push. POOL_BOUNDARY is the Sentinel object, which is a macro with a value of nil that marks the boundary of an automatic pool release.

When autoreleasepool is initialized, it internally calls the objc_autoreleasePoolPush method

Autoreleasepool internally calls the objc_autoreleasePoolPop method when it calls the destructor torelease

1. objc_autoreleasePoolPushInto 桟

A push operation creates a new autoreleasepool, and the AutoreleasePoolPage is implemented by inserting a POOL_SENTINEL into the next position of the AutoreleasePoolPage. And return the memory address of the inserted POOL_SENTINEL.

static inline void *push() { id *dest; If (slowPath (DebugPoolAllocation)) {// If not, create dest = autoreleaseNewPage autoreleaseNewPage(POOL_BOUNDARY); } else {// If so, push the sentinel object by autoreleaseFast, dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }Copy the code

Objc_autoreleasePoolPush source analysis

  1. Check whether the pool exists
  2. If not, it is created through the autoreleaseNewPage method
  3. If so, the sentinel object is pressed via autoreleaseFast
(1) autoreleaseFast
static inline id *autoreleaseFast(id obj) { //1. AutoreleasePoolPage *page = hotPage(); If (page &&! Page ->full()) {// If not fully, then stack return page->add(obj); } else if (page) {return autoreleaseFullPage(obj, page);} else if (page) {return autoreleaseFullPage(obj, page); } else {return autoreleaseNoPage(obj); }}Copy the code

AutoreleaseFast method stack object, source code interpretation

  1. Gets the current action page, hotPage, and determines whether the page exists and is full
  2. If the page exists and is not full, the object is pressed through the add method
  3. If the page exists and is full, arrange a new page through the autoreleaseFullPage method
  4. If the page does not exist, a new page is created using the autoreleaseNoPage method

hotPage

Static inline AutoreleasePoolPage *hotPage() {AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; }Copy the code

autoreleaseNoPage

Id *autoreleaseNoPage(id obj) {id *autoreleaseNoPage(id obj) {id *autoreleaseNoPage(id obj) { hotPage()); bool pushExtraBoundary = false; // Check whether it is empty placeholder, if so, then stack sentinel id = yes if (haveEmptyPoolPlaceholder()) {pushExtraBoundary = true; } else if (obj! = POOL_BOUNDARY && DebugMissingPools) { _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY &&! DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); } // Initialize the first page AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); // Set page to current page. // If the stack sentinel id is yes, then the stack sentinel object if (pushExtraBoundary) {page->add(POOL_BOUNDARY); // The stack sentinel object if (pushExtraBoundary) {page->add(POOL_BOUNDARY); // Stack with object} // Stack with object return page->add(obj); }Copy the code

No hotPage. Create a hotPage and add it. At this point, since there is no AutoreleasePoolPage in memory, the AutoreleasePoolPage is built from scratch, so that the current page table, as the first page table, has no parent pointer.

autoreleaseFullPage

/** Add an AutoreleasePoolPage object and call this method when the page is full * Iterate until an AutoreleasePoolPage is found * If it is not found, Create a new AutoreleasePoolPage * and use the found or built page as the hotPage. AutoreleaseFullPage (id obj, AutoreleasePoolPage *page) {ASSERT(page == hotPage()); ASSERT(page->full() || DebugPoolAllocation); If (page->child) page = page->child; if (page->child) page = page->child; Else Page = new AutoreleasePoolPage(page); else Page = new AutoreleasePoolPage(page); } while (page->full()); // Set to the current operation page setHotPage(page); // The object stack with return Page ->add(obj); }Copy the code

add

// Stack object ID *add(id obj) {ASSERT(! full()); unprotect(); // Pass in the location where the object is stored (faster than 'return next-1' because of aliases) id *ret = next; // the obj press stack to the next pointer position, then next stack ++, that is, the next object storage position *next++ = obj; protect(); return ret; }Copy the code
(2) autoreleaseNewPage
id *autoreleaseNewPage(id obj) { //1. AutoreleasePoolPage *page = hotPage(); If (page) return autoreleaseFullPage(obj, page); if (page) return autoreleaseFullPage(obj, page); //2.2 Create a page with the autoreleaseNoPage method else return autoreleaseNoPage(obj); }Copy the code

AutoreleaseNewPage Source code analysis

  1. Obtain the current page from hotPage to determine whether the current page exists
  2. If it does not exist, the page is created through the autoreleaseNoPage method
  3. If so, the object is pressed through the autoreleaseFullPage method

2. objc_autoreleasePoolPopThe 桟

We usually pass a sentinel object POOL_SENTINEL in this method and release the object as shown below:

static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; If (token == (void*)EMPTY_POOL_PLACEHOLDER) {// Popping the top-level placeholder pool. If it is an empty placeholder, get the current page page = hotPage(); if (! Page) {// Pool was never used. Clear the placeholder. // Return setHotPage(nil) if the current page does not exist; } // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. // If the current page exists, set the current page to coldPage and token to the starting position of coldPage page = coldPage(); token = page->begin(); } else {// If the page is empty, get the token page = pageForPointer(token); } stop = (id *)token; // If the last position is not a sentinel, the last position is an object if (*stop! = POOL_BOUNDARY) {if (stop == page->begin() &&! page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, Leaving the cold Page in place // 2. An object is autoreleased with no pool} else {// If it is in the first place, there is an Error. For bincompat purposes this is not // fatal in executables built with old SDKs. return badPop(token); } } if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) { return popPageDebug(token, page, stop); } // Out stack page return popPage<false>(token, page, stop); }Copy the code
1. PopPage out stack page
template<bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); // Out the current operation page object page->releaseUntil(stop); If (allowDebug && DebugPoolAllocation && page->empty()) {allowDebug && DebugPoolAllocation && page->empty()) {allowDebug && DebugPoolAllocation && page->empty() *parent = page->parent; Page ->kill(); // Set the parent page to the current operation page. } else if (allowDebug && DebugMissingPools && page->empty() && ! Page ->parent) {// Delete all pop(top) page->kill(); // delete all pop(top) page->parent (); setHotPage(nil); } else if (page->child) {if (page->lessThanHalfFull()) {page->child->kill(); } else if (page->child->child) { page->child->child->kill(); }}}Copy the code
2. releaseUntilReleases all objects before the stop position
void releaseUntil(id *stop) { // Not recursive: We don't want to blow out the stack // If a thread accumulates a stupendous amount of garbage // Determine whether the next object is While (this->next!) = stop) { // Restart from hotPage() every time, In case release // AutoreleasePoolPage *page = hotPage(); // Fixme I think this' while 'can be' if ', but I can't prove it // If (page->empty()) {page = page->parent; // Set page to parent setHotPage(page); } page->unprotect(); // Next conduct -- operation, that is, the stack ID obj = *--page->next; Memset ((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); // If (obj! = POOL_BOUNDARY) { objc_release(obj); }} setHotPage(this); // We expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { ASSERT(page->empty()); } #endif }Copy the code

ReleaseUntil Source code parsing

Through the loop, judge whether the object is equal to stop, its purpose is to release all the objects before stop, first by obtaining page next to release the object (that is, the last object of page), and next decrements, get the last object, judge whether it is sentinel object, If not, then objc_release is automatically called

3. Kill () Destroys the current page

The kill implementation, which basically destroys the current page, assigns the current page to the parent page and sets the child object pointer to the parent page to nil

void kill() { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage AutoreleasePoolPage *page = this; While (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; Page = page->parent; if (page) { page->unprotect(); Page ->child = nil; page->protect(); } delete deathptr; } while (deathptr ! = this); }Copy the code

3. Into the stack out of the summary

1) pressure stack (push)

Pressing common objects in a page is done primarily by incrementing the next pointer

  • When there is no pool, that is, only empty placeholders (stored in TLS), the page is created and the sentinel object is pressed

  • When the page is full, insert the AutoRelease object at the top of the stack where the next pointer points. (Sending an AutoRelease message to an object means adding the object to the top of the stack where the next pointer points to the current AutoreleasePoolPage.)

  • When the page is full (the next pointer immediately points to the top of the stack), create the page object for the next page and set the page child object to the new page. The next pointer for the new page is initialized at the bottom of the stack (begin position). Next time, you can continue to add new objects to the top of the stack.

2) 出桟(pop)

The stack removal of ordinary objects from the page is done primarily by decrementing the next pointer

  • Find the page on which the sentinel object is located based on the incoming Sentinel object address

  • In the current page, all autoRelease objects inserted after the Sentinel object are sent a -release message once, and the next pointer is moved back to the correct position. Clean from higher addresses to lower addresses)

  • When the page is empty, the parent object of the page to be assigned is the current page

Interpreting the Main function

int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

In the terminal, use the clang-rewrite-objc main.m command to rewrite the OC code as the C++ implementation.

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_49_sdbnp0nd07q4m_sh4_gw52r40000gn_T_main_9e48ee_mi_0);
    }
    return 0;
}
Copy the code

By comparison, By declaring a local variable of type __AtAutoreleasePool,@autoreleasepool is converted to the __AtAutoreleasePool structure so that the entire iOS application is contained in an autoreleasepool block @autoreleasepool{} is essentially a structure:

struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; };Copy the code

This structure calls the: objc_autoreleasePoolPush() method when it is initialized and the: objc_autoreleasePoolPop method when it is destructed. This shows that when main actually works, it looks like this:

int main(int argc, Const char * argv[]) {{// atautoreleasepoolobj = const char * argv[] {// atautoreleasepoolobj = const char * argv[] objc_autoreleasePoolPush(); // do things you want return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); objc_autoreleasePoolPop(atautoreleasepoolobj); } return 0; }Copy the code

Local release tank

1. Create a new automatic release pool

Under the ARC

@autoreleasepool {
  Student *s = [[Student alloc] init];
}
Copy the code

Under the MRC

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init;
Student *s = [[Student alloc] init];
[pool drain];
Copy the code

The object s is added to the automatic release pool when ARC code is executed in close curly bracket (equivalent to MRC code execution [pool drain];). A release is performed on all the objects in the pool in turn

So in that case, let me write it this way under ARC

{
  Student *s = [[Student alloc] init];
}
Copy the code

For MRC let me just write it like this

Student *s = [[Student alloc] init];
[s release];
Copy the code

The effect is exactly the same as if you were using an automatic free pool, so why should I use it? Initializing objects is a waste of time and memory.

The Autoreleasepool used above is called a local releasepool. With that in mind, let’s look at the application of a local releasepool

2. Local release pool application

Look at this code

for (int i = 0; i < largeNumber; i++) { 
    NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
    str = [str stringByAppendingString:@" - world"]; 
}
Copy the code
  • The [NSString stringWithFormat:@”hello -%04d”, I] method creates an object that is added to the automatic release pool, and the release of the object is given to the RunLoop release pool
  • A RunLoop’s release pool will release once when the RunLoop is about to go to sleep or exit
  • The thread is doing something all the time in the for loop, and the Runloop does not go to sleep
  • The NSString generated by the for loop in the above code will not be released in time, resulting in a transient memory drain

The solution is to manually create a local release pool each time you loop, create it in time, release it in time, so that NSString objects will be released in time

for (int i = 0; i < largeNumber; i++) { @autoreleasepool { NSString *str = [NSString stringWithFormat:@"hello -%04d", i]; str = [str stringByAppendingString:@" - world"]; }}Copy the code

Generating UIImage objects using imageNamed: and so on in the for loop is probably more of a chore, and memory can be killed by the system at any time. In this case, using Autoreleasepool can significantly reduce the memory footprint of your application.

3. When is the Autorelease object released

When a local releasepool is created, it is released at the end of the @autoReleasepool {} close curly bracket. Releasing objects in time greatly reduces the program’s memory usage. Without manually adding @Autoreleasepool, the Autorelease object is released at the end of the current runloop iteration (one runloop per thread), and it can be released because the system adds an automatic releasepool to each Runloop iteration.

The interview questions

  1. When are temporary variables released?

If a temporary variable is added to the automatic releasepool, it will be released after the runloop has gone to sleep or after the Autoreleasepool scope

  1. AutoreleasePool principle

The essence of an AutoreleasePoolPage is an AutoreleasePoolPage object, which is a stack of pages. Each AutoreleasePoolPage is a two-way linked list that connects the pressing and unloading of the autoreleasepolPool by calling the underlying objc_autoreleasePoolPush and objc_autoreleasePoolPop through the constructor and destructor of the structure. We call AutoreleasePoolPage’s push and pop methods and every time we call push we create a new AutoreleasePoolPage, The AutoreleasePoolPage inserts a POOL_BOUNDARY and returns the memory address where the POOL_BOUNDARY was inserted. While push calls autoreleaseFast method internally, there are mainly the following three cases

  • When the page exists and is not, the add method is called to add the object to the next pointer to the page, incrementing next
  • When the page exists and is full, a call to autoreleaseFullPage initializes a new page, and then the add method is called to add the object to the page stack
  • When the page does not exist, the autoreleaseNoPage is called to create a hotPage, and then the add method is called to add the object to the page stack
  • When the POP operation is performed, a value is passed in. This value is the return value of the push operation, which is the memory address token of POOL_BOUNDARY.

So the internal implementation of POP is to find the sentinel object on the page based on the token, and then use objc_release to release the object before the token and put the next pointer to the correct location

  1. Can AutoreleasePool be nested?

Use can be nested, its purpose is to control the application of memory peak, so that it is not too high Can be nested because automatically release the pool on the stack for the node, connected via a two-way linked list, and automatically release pool of threads and one to one correspondence of the multilayer nested is constantly pushs sentinel objects, when pop, will release the first, In the release of outside

  1. Which objects can be added to AutoreleasePool? Is alloc ok?

Objects that are generated using the new, alloc, copy keywords and objects that are retained need to be manually released, they are not added to the automatic release pool. Objects that are set to autoRelease do not need to be manually freed, they go directly to the automatic release pool. All autorelease objects, after they are out of scope, Interview Question 5: When will the AutoreleasePool be released? The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler (). The first event monitored by the Observer is an Entry(about to enter the Loop), which calls _objc_autoreleasePoolPush() within its callback to create an automatic release pool. Its order is -2147483647, which has the highest priority, ensuring that the create release pool occurs before all other callbacks. The second Observer monitors two events: BeforeWaiting(ready to go to sleep) calls _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() releases old pools and creates new ones; BeforeWaiting(ready to go to sleep) calls _objc_autoreleasePoolPush() releases old pools and creates new pools; Call _objc_autoreleasePoolPop() when Exit(that is, to Exit the Loop) torelease the automatic release pool. The order of this Observer is 2147483647, which has the lowest priority, ensuring that its pool release occurs after all other callbacks.

  1. The relationship between Thread and AutoreleasePool

Each thread, including the main thread, maintains its own autofree pool stack structure and new autofree pools are added to the top of the stack when they are created; When the auto-release pool is destroyed, it is removed from the stack. For the current thread, the auto-release object is placed on the top of the auto-release pool. When a thread stops, all automatic release pools associated with that thread are automatically released. Each thread has an automatic pool release stack structure associated with it. When a new pool is created, it is pushed to the top of the stack, and when a pool is destroyed, it is removed from the stack. For the current thread, the released object is pushed to the top of the stack. The RunLoop in the main program automatically creates an AutoreleasePool before each event loop and drains the object at the end of the event loop