# autorelease pool = autorelease pool = autorelease pool

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

Let’s look at the c++ implementation of main.m using the clang command:

 clang -rewrite-objc main.m -o main.cpp
Copy the code

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    }
    return 0;
}
Copy the code
struct __AtAutoreleasePool {
  __AtAutoreleasePool() 
{
   atautoreleasepoolobj = objc_autoreleasePoolPush();
 }
  ~__AtAutoreleasePool() 
{
   objc_autoreleasePoolPop(atautoreleasepoolobj);
}
   void * atautoreleasepoolobj;
};
Copy the code

__AtAutoreleasePool has two functions, __AtAutoreleasePool() and ~__AtAutoreleasePool(). In c++, the struct function is called at initialization, and the ~+ struct function is called at destruct. For example: define a structure (to support c++ compilation, change main.m to main.mm), then declare an object, look at log:

So we can conclude that the auto-release pool executes the objc_autoreleasePoolPush function during initialization and the objc_autoreleasePoolPop function during destruction.

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

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

AutoreleasePoolPage: : push () : : : is an attribute function, explain AutoreleasePoolPage is a structure object, to view its data structure (listed in order to facilitate understanding, only its properties and we need to look at the functions) :

     /**
    struct magic_t {
        # define EMPTY_POOL_PLACEHOLDER ((id*)1
        # define POOL_BOUNDARY nil
        static const uint32_t M0 = 0xA1A1A1A1;
        #define M1 "AUTORELEASE!"static const size_t M1_len = 12; uint32_t m[4]; */ magic_t const magic; // uint32_t = 1; Pthread_t const thread; //8 bit AutoreleasePoolPage * const parent; //8 bits pointing to the next page AutoreleasePoolPage *child; // uint32_t const depth; / / four uint32_t hiwat; //4 bits static void * operator new(size_t size) {return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
Copy the code

Let’s figure out how many bits these attributes take up: add them up to get a number: 56. Next, look at the SIZE of the new function. According to the macro definition, the number is 4096, which is the SIZE of an AutoreleasePoolPage

    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

#define PAGE_MAX_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096 /* bytes per 80386 page */
Copy the code

### When to use Autoreleasepool?

  1. A large number of temporary variables;
  2. Some non-UI operations;
  3. Self-created helper threads and so on.

Each page can hold 4096 bytes, and it occupies 56 bytes, so the maximum number of objects per page is :(4096-56) / 8 = 505. Continue testing the code and print out the autoreleasepool information with void_objc_autoreleasePoolPrint (void_objc_autoreleasePoolPrint is a system function, removed here for debugging purposes) :

extern void_objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for(int i = 0; i < 505; i++) { NSObject * obj = [[NSObject alloc] init]; void_objc_autoreleasePoolPrint(); }}return 0;
}

objc[42414]: AUTORELEASE POOLS for thread 0x1000bd5c0
objc[42414]: 506 releases pending.
objc[42414]: [0x103003000]  ................  PAGE  (hot) (cold)
objc[42414]: [0x103003038]  ################ POOL 0x103003038
objc[42414]: [0x103003040]       0x10061d340  NSObject
objc[42414]: [0x103003048]       0x100604c70  NSObject
objc[42414]: [0x103003050]       0x100606ba0  NSObject
objc[42414]: [0x103003058]       0x1006079b0  NSObject
objc[42414]: [0x103003060]       0x10061d150  NSObject
objc[42414]: [0x103003068]       0x100614f70  NSObject
objc[42414]: [0x103003070]       0x100615280  NSObject
objc[42414]: [0x103003078]       0x100611700  NSObject
objc[42414]: [0x103003080]       0x10060ff80  NSObject
objc[42414]: [0x103003088]       0x10060cc30  NSObject
objc[42414]: [0x103003090]       0x100609e30  NSObject
objc[42414]: [0x103003098]       0x100609fd0  NSObject
objc[42414]: [0x1030030a0]       0x100605a00  NSObject
objc[42414]: [0x1030030a8]       0x100607810  NSObject
...
objc[42439]: [0x103002fe0]       0x10061e840  NSObject
objc[42439]: [0x103002fe8]       0x10061e850  NSObject
objc[42439]: [0x103002ff0]       0x10061e860  NSObject
objc[42439]: [0x103002ff8]       0x10061e870  NSObject
objc[42439]: [0x101005000]  ................  PAGE  (hot) 
objc[42439]: [0x101005038]       0x10061e880  NSObject
objc[42439]: # # # # # # # # # # # # # #
Program ended with exit code: 0
Copy the code

The status of the first page is hot->cold. The status of the second page is hot. The first page is full and the second page is hot. But why is this different from what we just figured out? 505 should be exactly one page, why is there a second page? This is because there is a POOL_BOUNDARY when adding the first object. The purpose of the boundary is to indicate when the object is being deleted. Stop deleting at the boundary.

Autoreleasepool is a stack structure consisting of a number of AutoreleasePoolPage pairs (parent and child Pointers respectively). # # objc_autoreleasePoolPush then AutoreleasePoolPage * * : : push () * * source analysis, and dived into the push:

    static inline void *push() 
    {
        id *dest;
        if(DebugPoolAllocation) {DEBUG mode dest = autoreleaseNewPage(POOL_BOUNDARY); }else//// add a sentinel object to the autoreleaseFast(POOL_BOUNDARY); }return dest;
    }
Copy the code

###autoreleaseFast

static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); // Get the current hot pageif(page && ! Page ->full())return page->add(obj);
        } else if(page) {// I got it but it's fullreturn autoreleaseFullPage(obj, page);
        } else{// not foundreturnautoreleaseNoPage(obj); }}Copy the code

autoreleaseFullPage

Static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {// onewhileAdd obj to page->add(obj); add obj to page->add(obj)do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

    AutoreleasePoolPage(AutoreleasePoolPage *newParent) 
        : magic(), next(begin()), thread(pthread_self()),
          parent(newParent), child(nil), 
          depth(parent ? 1+parent->depth : 0), 
          hiwat(parent ? parent->hiwat : 0)
    { 
        if(parent) { parent->check(); assert(! parent->child); parent->unprotect(); parent->child = this; // Make its parent's child point to itself, keeping it bidirectional to parent->protect(); } protect(); } page->add(obj); id *add(id obj) { unprotect(); id *ret = next; // get the current next pointer *next++ = obj; // Make next point to obj and next++ protect();return ret;
    }
Copy the code

autoreleaseNoPage

    id *autoreleaseNoPage(id obj)
    {
        bool pushExtraBoundary = false;
        if(haveEmptyPoolPlaceholder()) {pushExtraBoundary = pushExtraBoundary =true;
        }
        else if(obj ! = POOL_BOUNDARY && DebugMissingPools) {objc_autoreleaseNoPool(obj);return nil;
        }
        else if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) {// Sets the placeholderreturn setEmptyPoolPlaceholder();
        }

        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if(pushExtraBoundary) { page->add(POOL_BOUNDARY); // start by adding the POOL_BOUNDARY}returnpage->add(obj); // Finally execute page->add}Copy the code

To sum up:

  1. When there is no page, create the first page and add a boundary character.
  2. Start adding objects normally;
  3. Create a new page and set it to hot. The parent becomes cold ##objc_autoreleasePoolPop
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 ->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. This deletes every object in each pageif (DebugPoolAllocation  &&  page->empty()) {
            AutoreleasePoolPage *parent = page->parent;
            page->kill(a); // Kill yourselfsetHotPage(parent); // set parent to hot}else if(DebugMissingPools && page->empty() && ! Page ->parent) {// There is no parent in 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); } } } void releaseUntil(id *stop) {while(this->next ! = stop) { AutoreleasePoolPage *page = hotPage();while(page->empty()) { page = page->parent; // All the objects in page have been deleted. Point page to parent and set it to hotsetHotPage(page); } page->unprotect(); id obj = *--page->next; Memset ((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect();if(obj ! = POOL_BOUNDARY) { objc_release(obj); Release}} to each object in the pagesetHotPage(this);
    }

//page killOperating voidkill() 
    {
        AutoreleasePoolPage *page = this;
        while(page->child) page = page->child; // Recursively find the bottom page AutoreleasePoolPage *deathptr;do{ deathptr = page; page = page->parent; / / points to the parentif(page) { page->unprotect(); page->child = nil; // Delete page->protect(); } delete deathptr; }while(deathptr ! = this); }Copy the code

To sum up:

  1. Delete every object in the page, and then collate the page pointing to parent as hot, recursively
  2. Delete all empty pages

So, once out of autoReleasepool’s scope space, all objects are released.

Apple has registered two observers in the main thread runloop: 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: BeforeWaiting(ready to go to sleep) and Exit(about to Exit Loop), BeforeWaiting(ready to go to sleep) calls _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() torelease old pools and create new ones; _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.