Today, when I was looking at Advanced Programming in Objective-C for iOS and OS X, multithreading and memory management, I turned to the chapter on AutoRelease, but the description of this section was vague, so I went to see the relevant code. The logic is as follows:

AutoReleasePool: Automatically releases the pool in iOS. As we all know, iOS uses reference counting to manage the memory of objects. When we create an XCode project, we will find this code in main.m:

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

The purpose of @Autoreleasepool is to manage reference counts for objects in this scope. Using clang-rewrite-objc main.m, we can see the following code from main. CPP:

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

@autoreleasepool {} adds objc_autoreleasePoolPush and objc_autoreleasePoolPop(atautoreleasepoolobj) at the end of the code. So let me go ahead and see what these two functions are doing?

From the Autoreleasepool layer, objects are managed via autoreleasepool. Each page has a fixed size, so what if there are a certain number of objects that need to be managed and the page becomes unmanageable? Create a new page, of course, and Apple links pages together in a two-way list. Each page has parent and Child Pointers to other pages. When an autoreleasepool object is added, we add another sentinel to split and return the memory address of the sentinel. So when we release, we just need to find the memory address corresponding to the sentinel, and then continue to release until the next sentinel value.

Autoreleasepool objects are released when the scope is over. Most people think that they are released when the scope is over. If the autoreleasepool is not manually added, a uniform release operation should be performed after the runloop ends. Each runloop automatically adds a push and pop code.

So with that in mind, let’s look at the code for matching drinks:

objc_autoreleasePoolPush

static inline void *push() 
{
    id *dest;
    /**
    halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
    */
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
Copy the code

Here we need to focus on two methods:

* autoreleaseNewPage(POOL_BOUNDARY);
* autoreleaseFast(POOL_BOUNDARY);
Copy the code

POOL_BOUNDARY is the sentinel value. Let’s move on to the implementation of these two methods:

Static inline ID *autoreleaseFast(id obj) { static pthread_key_t const key = AUTORELEASE_POOL_KEY; AutoreleasePoolPage *page = hotPage(); if (page && ! Page ->full()) {// return page->add(obj); } else if (page) {return autoreleaseFullPage(obj, page);} else if (page) {return autoreleaseFullPage(obj, page); return autoreleaseFullPage(obj, page); } else {// If there is no page, create a new page with parent nil. return autoreleaseNoPage(obj); }}Copy the code

The simplified method of adding OBj to enter page is as follows:

id *add(id obj) {
	id *ret;
	ret = next;  // faster than `return next-1` because of aliasing
  *next++ = obj;
   return ret;
}
Copy the code

Put the value of obj into the address of next, and then next moves down. It’s just a push operation. When we call Add, we have already checked the page, so there will be no overflow.

objc_autoreleasePoolPop

void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); } static inline void pop(void *token) { page = pageForPointer(token); stop = (id *)token; return popPage<false>(token, page, stop); } static AutoreleasePoolPage *pageForPointer(const void *p) { return pageForPointer((uintptr_t)p); } static AutoreleasePoolPage *pageForPointer(uintptr_t p) { AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; ASSERT(offset >= sizeof(AutoreleasePoolPage)); result = (AutoreleasePoolPage *)(p - offset); result->fastcheck(); return result; } template<bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); page->releaseUntil(stop); // memory: delete empty children if (allowDebug && DebugPoolAllocation && page->empty()) { // special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; page->kill(); setHotPage(parent); } else if (allowDebug && DebugMissingPools && page->empty() && ! page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); }}}Copy the code

To summarize the code here, we get the sentinel’s address through the previous push method, and then through a series of transformations, we can get the page where the pop is, by constantly pushing obj off the stack until next= sentinel’s address stops.

page->releaseUntil(stop); // The main outbound logicCopy the code