AutoreleasePool is an automatic memory reclamation mechanism in OC that delays the addition of variable releases in AutoreleasePool. Under normal circumstances, created variables are released when they are out of scope, but if you add variables to AutoreleasePool, release is delayed.

AutoreleasePool is created and released

  • The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler ().
  • The first Observer monitors an event called Entry(about to enter Loop), which creates an automatic release pool within its callback by calling _objc_autoreleasePoolPush(). Its order is -2147483647, the highest priority, ensuring that the release pool is created before all other callbacks.
  • The second Observer monitors two events: calling _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() while waiting (ready to sleep) torelease the old pool and create a new one; _objc_autoreleasePoolPop() is called upon Exit(about to Exit the Loop) torelease the automatic release pool. The order of this Observer is 2147483647, the lowest priority, ensuring that its release pool occurs after all other callbacks.
  • The code that executes on the main thread is usually written inside such callbacks as event callbacks and Timer callbacks. These callbacks are surrounded by AutoreleasePool created by RunLoop, so there is no memory leak and the developer does not have to show that the Pool was created.

That is, AutoreleasePool is created before a RunLoop event starts (push), and AutoreleasePool is released before a RunLoop event ends (POP). Autorelease objects in AutoreleasePool are added in a RunLoop event and released when AutoreleasePool is released.

Implementation principles of AutoreleasePool

Use the clang-rewrite-objc command in the terminal to rewrite the following OC code as a C++ implementation:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
 
    return 0;
}
Copy the code

In the CPP file code we can find the main function code as follows:

int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
    }

    return 0;
}
Copy the code

You can see that Apple implements @Autoreleasepool {} by declaring a local variable __autoreleasepool of type __AtAutoreleasePool. __AtAutoreleasePool is defined as follows:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

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

Based on the characteristics of constructors and destructors (the constructor of automatic local variables is called when the program executes to the point where the object is declared, and the destructor is called when the program executes to the point where the object is declared), we can simplify the above two pieces of code into the following form:

int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ {
        void *atautoreleasepoolobj = objc_autoreleasePoolPush();
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }

    return 0;
}
Copy the code

Objc_autoreleasePoolPush () > [Object AutoRelease] > objc_autoreleasePoolPop(void *).

Take a look at objc_autoreleasePoolPush and objc_autoreleasePoolPop implementations:

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

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

The above method appears to encapsulate the static methods push and POP corresponding to AutoreleasePoolPage. The implementation of AutoreleasePoolPage is analyzed below and the implementation principle of AutoreleasePool is revealed.

AutoreleasePoolPage implementation

AutoreleasePoolPage introduction

AutoreleasePoolPage is a C++ class defined in nsobject. mm as follows:

class AutoreleasePoolPage {
# define EMPTY_POOL_PLACEHOLDER ((id*)1)

# define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};
Copy the code
  • Magic checks variables for verification integrity
  • Next points to the newly added AutoRelease object
  • Thread Page Specifies the current thread. AutoreleasePool is thread-to-thread (thread pointer in the structure points to the current thread)
  • Parent The parent node points to the previous page
  • Child points to the next page
  • Depth Depth of the linked list and number of nodes
  • Hiwat High Water Mark an upper limit for data capacity
  • EMPTY_POOL_PLACEHOLDER Empty pool placeholder
  • POOL_BOUNDARY is a nil boundary object, the previous source variable name was POOL_SENTINEL object, used to distinguish each page i.e. each AutoreleasePoolPage boundary
  • PAGE_MAX_SIZE = 4096, why 4096? Virtual memory is 4096 bytes per sector,4K aligned.
  • COUNT Number of objects in a page

Two-way linked list

AutoreleasePool does not have a single structure. Instead, it is a stack structure composed of several autoreleasepoolpages (corresponding to the parent pointer and child pointer in the structure).

Parent and child are the Pointers used to construct a bidirectional list. Parent points to the previous page, child to the next page. When an AutoreleasePoolPage space is used up, an AutoreleasePoolPage object is created and linked to the list. Subsequent AutoReleases are added to the new page.

objc_autoreleasePoolPush

Every time the auto-release pool calls objc_autoreleasePoolPush, it puts the boundary object at the top of the stack and returns the boundary object for release.

atautoreleasepoolobj = objc_autoreleasePoolPush();
Copy the code

Atautoreleasepoolobj is the returned boundary object (POOL_BOUNDARY)

Push is implemented as follows:

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

It calls the AutoreleasePoolPage class method push:

static inline void *push() {
   return autoreleaseFast(POOL_BOUNDARY);
}
Copy the code

One of the key methods here is called autoreleaseFast and the POOL_BOUNDARY object is passed in:

static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if(page && ! page->full()) {return page->add(obj);
   } else if (page) {
       return autoreleaseFullPage(obj, page);
   } else {
       returnautoreleaseNoPage(obj); }}Copy the code

There are three ways to select different code for execution:

  • If you have a hotPage and the current page is not available, call page->add(obj) to add the object to the stack of AutoreleasePoolPage
  • If you have a hotPage and the current page is full, call autoreleaseFullPage to initialize a new page and call page-> Add (obj) to add the object to the stack of AutoreleasePoolPage
  • If there is no hotPage, call autoreleaseNoPage to create a hotPage. Call Page -> Add (obj) to add the object to the stack of AutoreleasePoolPage

The last one will call Page -> Add (obj) to add the object to the automatic release pool. HotPage is the AutoreleasePoolPage that is currently in use.

AutoreleasePoolPage::autorelease(id obj)

To implement the autoRelease method, look at the call stack of the method:

- [NSObject autorelease] └── id () └─ id () ├ ─ 088 (2 Obj AutoreleasePoolPage: : autorelease (id) └ ─ ─ the static id AutoreleasePoolPage: : autoreleaseFast (id obj) ├ ─ ─ id * add (id obj) ├── Static Id *autoreleaseFullPage(Id obj, ├─ ├─ ├─ id *add(id obj) ├─ static ID ├── ├─ ├─ ├─ ├─ ├─ Id * Add (Id obj)Copy the code

In the call stack of the AutoRelease method, the autoreleaseFast method mentioned above is eventually called to add the current object to the AutoreleasePoolPage.

The methods in this section are easy to implement, with a few parameter checks and a final call to the autoreleaseFast method:

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

    return rootAutorelease2();
}

__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
   id *dest __unused = autoreleaseFast(obj);
   return obj;
}
Copy the code

As with push, the key code for autoRelease is to call autoreleaseFast to add an object to the auto-release list stack, but push pushes a boundary object, The autoRelease function pushes the object that needs to be added to the AutoReleasepool.

objc_autoreleasePoolPop

Automatic release pool release is the boundary object returned by the pass push,

objc_autoreleasePoolPop(atautoreleasepoolobj);
Copy the code

Atautoreleasepoolobj is the returned boundary object (POOL_BOUNDARY).

AutoreleasePoolPage: : pop () :

Static inline void pop(void *token) // The token pointer points to the address at the top of the stack {AutoreleasePoolPage *page; id *stop; page = pageForPointer(token); // Page stop = (id *)token;if(DebugPoolAllocation && *stop ! = POOL_SENTINEL) { // This check is not valid with DebugPoolAllocation off // after an autorelease with a pool page but no poolin place.
        _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                    token);
    }

    if (PrintPoolHiwat) printHiwat(); // record the highest watermark mark page->releaseUntil(stop); // Start at the top of the stack and send a release message to the objects in the stack until the first sentry object is encountered // Memory: delete Empty children // Delete the empty nodeif (DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill(a);setHotPage(parent);
    } else if(DebugMissingPools && page->empty() && ! page->parent) { // specialcase: delete everything for pop(top) 
        // when debugging missing autorelease pools
        page->kill(a);setHotPage(nil);
    } 
    else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full
        if (page->lessThanHalfFull()) {
            page->child->kill(a); }else if (page->child->child) {
            page->child->child->kill(a); }}}Copy the code

This process is mainly divided into two steps:

  • Page ->releaseUntil(stop), call objc_release() on all objects between the top of the stack (page->next) and the stop address (POOL_SENTINEL), and subtract 1 from the reference count
  • Empty the page object page->kill(), with two comments
// hysteresis: keep one empty child if this page is more than half full

// special case: delete everything forPop (0) unless called pop(0), which clears up all page objects; Otherwise, an empty child page will be left when the current page contains more than half of the objects, so that a new page may be needed immediately to save the overhead of creating a page.Copy the code

summary

  • AutoreleasePoolPage a page is 4096 bytes in size. Each AutoreleasePoolPage is connected to a bidirectional list to form an autoreleasepoolpool
  • When an object calls the AutoRelease method, it is added to the stack of AutoreleasePoolPage
  • Pop is passing in boundary objects and then sending a release message to the objects in the page