preface

The previous article introduced the source code for reference counting and weak references. This article explores the source code for autorelease pooling and autorelease

autorelease

Autorelease is used to delay the release of objects.

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}
Copy the code
inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return(id)this; // If the type is tagged pointer, the current object is returnedif (prepareOptimizedReturn(ReturnAtPlus1)) return(id)this; // If prepareOptimizedReturn is True, return the current object directlyreturnrootAutorelease2(); RootAutorelease2}Copy the code

Let’s break down the function

  • prepareOptimizedReturn

PrepareOptimizedReturn is used to optimize the return and is implemented as follows

static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) { assert(getReturnDisposition() == ReturnAtPlus0); // Ensure that the current acquired Disposition isfalse

    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {// If optimizedreturn is currently allowedif (disposition) setReturnDisposition(disposition); // Set ReturnDisposition totrue
        return true;
    }

    return false;
}
Copy the code
  • rootAutorelease2

RootAutorelease2 is implemented simply by calling the AutoRelease method of AutoreleasePoolPage to drop the object into the automatic release pool

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2() { assert(! isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}
Copy the code

Automatic releasepool

First, take a look at the Autoreleasepool data structure

Then the above AutoreleasePoolPage: : autorelease (this) (id) the entrance, to analyze the source code from here

static inline id autorelease(id obj) { assert(obj); // Assert that the object is not empty (! obj->isTaggedPointer()); // Assert that the object is not TaggedPointer ID *dest __unused = autoreleaseFast(obj); assert(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); // Make sure dest does not exist or equal to EMPTY_POOL_PLACEHOLDER or objreturn obj;
}
Copy the code
  • autoreleaseFast

Then go down to the source code for autoreleaseFast

static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); // Get the current top pageif(page && ! Page ->full()) {// If the page exists and is not fullreturnpage->add(obj); // Add the object to the current page}else if(page) {// If the page exists but is already fullreturnautoreleaseFullPage(obj, page); // Call autoreleaseFullPage}else{// If page does not existreturnautoreleaseNoPage(obj); // Call autoreleaseNoPage}Copy the code

You can see that the AutoreleasePoolPage is composed of one or more AutoreleasepoolPages

  • hotPage

The hotPage method is used to get the current topmost page

static inline AutoreleasePoolPage *hotPage() { AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); // Use TLS to query available AutoreleasePoolPage objectsif ((id *)result == EMPTY_POOL_PLACEHOLDER) returnnil; // If the query result is EMPTY_POOL_PLACEHOLDER, return nilif(result) result->fastcheck(); // If result is not null, the fastCheck method is called, which does the work of thread detectionreturn result;
}
Copy the code

Key is defined as follows:

// Thread keys reserved by libc for our use.
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif

static pthread_key_t const key = AUTORELEASE_POOL_KEY;
Copy the code
  • full

The full method is used to check if the current AutoreleasePoolPage is full

id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

bool full() { 
    return next == end();
}
Copy the code

SIZE is defined as follows

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

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
Copy the code
  • add

The Add function adds an object to an AutoreleasePoolPage

id *add(id obj) { assert(! full()); Unprotect (); // Set read/write status to id *ret = next; // faster than `returnnext-1` because of aliasing *next++ = obj; protect(); // Set it to read-onlyreturn ret;
}
Copy the code
  • autoreleaseFullPage

The job of the autoreleaseFullPage function is to create an AutoreleasePoolPage and put the object in it

static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    // The hot page is full. 
    // Step to the next non-full page, adding a new page if necessary.
    // Then add the object to that page.
    assert(page == hotPage());
    assert(page->full()  ||  DebugPoolAllocation);

    do {
        if(page->child) page = page->child; // Assign page->child if page->child existselsepage = new AutoreleasePoolPage(page); // If not, create a new AutoreleasePoolPage}while (page->full());

    setHotPage(page); // Set the new AutoreleasePoolPage to hotPagereturnpage->add(obj); // Add the object to the new AutoreleasePoolPage}Copy the code

Constructor of AutoreleasePoolPage

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; parent->protect(); } protect(); }Copy the code
  • setHotPage

Set hotPage over TLS

static inline void setHotPage(AutoreleasePoolPage *page) 
{
    if (page) page->fastcheck();
    tls_set_direct(key, (void *)page);
}
Copy the code
  • autoreleaseNoPage

The autoreleaseNoPage method is called if you did not create a Pool but generated an Autorelease object. In this method, you automatically create a hotPage for you and call Page -> Add (obj) to add the object to the stack of AutoreleasePoolPage

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
{
    // "No page"could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet assert(! hotPage()); bool pushExtraBoundary =false;
    if(haveEmptyPoolPlaceholder()) {// We are pushing a second pool over the empty placeholder pool // or pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary =true;
    }
    else if(obj ! = POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no poolin place, 
        // and no-pool debugging was requested by environment.
        _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug", 
                     pthread_self(), (void*)obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return nil;
    }
    else if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) { // We are pushing a pool with no poolin place,
        // and alloc-per-pool debugging was not requested.
        // Install and return the empty pool placeholder.
        return setEmptyPoolPlaceholder(); // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);setHotPage(page);
    
    if(pushExtraBoundary) { page->add(POOL_BOUNDARY); // Add a nil delimit for each AutoreleasePoolPage // Push the requested object or pool.return page->add(obj);
}
Copy the code
  • haveEmptyPoolPlaceholder
static inline bool haveEmptyPoolPlaceholder()
{
    id *tls = (id *)tls_get_direct(key);
    return (tls == EMPTY_POOL_PLACEHOLDER);
}
Copy the code
  • setEmptyPoolPlaceholder
static inline id* setEmptyPoolPlaceholder()
{
    assert(tls_get_direct(key) == nil);
    tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
    return EMPTY_POOL_PLACEHOLDER;
}
Copy the code

Once you have seen the process of adding AutoreleasePoolPage, take a look at the process of releasing AutoreleasePoolPage. AutoreleasePool is essentially a bidirectional linked list. The process of releasing AutoreleasePool is to delete the list from the last linked list and then delete it from the next linked list, as shown below:

AutoreleasePoolPage:: POP

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; EMPTY_POOL_PLACEHOLDER: a placeholder for when the autofree pool is emptyif(token == (void*)EMPTY_POOL_PLACEHOLDER) {// If the value is EMPTY_POOL_PLACEHOLDER // Popping the value from the top-level placeholder pool.if(hotPage()) {// Pool was used. Pop its contents normally. // Pool pages remain remain of tokensforre-use as usual. pop(coldPage()->begin()); // Destroy the entire autorelease pool}else {
            // Pool was never used. Clear the placeholder.
            setHotPage(nil); Set hotPage to nil}return; } page = pageForPointer(token); Stop = (id *)token; stop = (id *)token; // Token is converted to stopif(*stop ! = POOL_BOUNDARY) {// If stop is not stored as a boundaryif(stop == page->begin() && ! page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold pageinAn object is autoreleased with no pool // There are two cases where the first object stored on the first node of the automatic release pool is not a boundary character: // 1. The top-level pool is not freed, but the first node is left.else {
            // Error. For bincompat purposes this is not 
            // fatal inExecutables built with old SDKs. // Not the first node of the auto-release pool, stop stores cases that are not boundary charactersreturnbadPop(token); // Call badPop()}}if (PrintPoolHiwat) printHiwat(); // If you want to print hiwat, print page->releaseUntil(stop); // Memory: delete empty childrenif (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) {// If page has child node // hysteresis: keep one empty childif page is more than half full
        if(Page ->lessThanHalfFull()) {// If the page has occupied less than half of the page->child->kill(a); //killDrop page child}else if(page - > child - > child) {/ / if the page take up the space is already more than half, child node has child nodes and page page - > child child -- - > >kill(a); //killDrop child node}}}Copy the code
  • pageForPointer

The addresses assigned to the AutoreleasePoolPage object are all 4096 aligned, that is, the addresses of the AutoreleasePoolPage object are all multiples of 4096. Therefore, when the token is converted to decimal, the value of 4096 is mod. You can obtain the offset of the token address to the AutoreleasePoolPage object address. Since the size of the AutoreleasePoolPage object itself is 56, if the token mod to 4096 is less than 56, an exception will be thrown. Otherwise, the offset subtracted from the token address is the address of the AutoreleasePoolPage object, which is converted to an AutoreleasePoolPage * pointer, which is the Page node where the token is located.

static AutoreleasePoolPage *pageForPointer(uintptr_t p) { AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; 4096 assert(offset >= sizeof(AutoreleasePoolPage)); Result = (AutoreleasePoolPage *)(p-offset); // If the remainder is smaller than the size of AutoreleasePoolPage, an exception is raised. // The decimal number p subtracts the remainder offset, resulting in an AutoreleasePoolPage * pointer result-> fastCheck (); // Check according to the configurationreturn result;
}
Copy the code
  • badPop

This function should not be called normally, only when using older SDKS. When badPop occurs, an error log is logged and the automatic release pool is destroyed.

static void badPop(void *token)
{
    // Error. For bincompat purposes this is not 
    // fatal inExecutables built with old SDKs. // This bug is not fatal to older SDKsif(DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0, 2_0)) {// OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal. Error calling badPop _objc_fatal ()"Invalid or prematurely-freed autorelease pool %p.", token); } // Bad Pop is warned once. // Under the Old SDK, Bad Pop will record a journal static bool rolling stock =false;
    if(! complained) { complained =true; // Output a series of messages to crashlogCrash _objC_inform_now_and_on_crash ("Invalid or prematurely-freed autorelease pool %p. "
             "Set a breakpoint on objc_autoreleasePoolInvalid to debug. "
             "Proceeding anyway because the app is old "
             "(SDK version " SDK_FORMAT "). Memory errors are likely.", token, FORMAT_SDK(sdkVersion())); } objc_autoreleasePoolInvalid(token); // Destroy the automatic release pool containing tokens}Copy the code
  • releaseUntil

The auto-release pool destroys the most important link in the object. The caller is found by using pageForPointer(), the page node where the token is located, and the parameter is token. Start with hotPage, use the next pointer to traverse the list of autoRelease objects stored in the node, release each object once, and clear the pointer to Next. If the hotPage is empty, the parent is looped forward and the next pointer is iterated over the parent until the next pointer points to token. Because the token is in this, the hotPage should be this.

Void releaseUntil(id *stop) {// Recursion is not used here to prevent stack overflowwhile(this->next ! // start hotPage from hotPage()in case-release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); / / remove hotPagewhile(page->empty()) {// Start with the node page and find the first non-empty node page = page->parent; // If the page is not empty, look at the parent node of the pagesetHotPage(page); // Set the new page node to HotPage} page->unprotect(); Id obj = *--page->next; Memset ((void*)page->next, SCRIBBLE, sizeof(*page->next)); // Clear the memory pointed to by next to SCRIBBLE page->protect(); // Set memory lock if necessaryif(obj ! Objc_release (obj); // Release the object}}setHotPage(this); // Set this object to hotPage#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        assert(page->empty());
    }
#endif
}
Copy the code
  • kill

All the objects that need to be released in the automatic release pool have been completed. At this time, all the page nodes after hotPage have been emptied, and the memory of these nodes needs to be reclaimed. The operation scheme is to recycle the memory from the last node to the caller node.

void killAutoreleasePoolPage *page = this; AutoreleasePoolPage *page = this; // Start with the callerwhile(page->child) page = page->child; AutoreleasePoolPage *deathptr;do{// Start traversing from the last node to the calling node deathptr = page; // keep the current traversal to the node page = page->parent; // walk forwardif(page) {// If there is a value page->unprotect(); // If necessary, unlock memory lock page->child = nil; // child null page->protect(); // Set memory lock if necessary} delete deathptr; // Reclaim the node that was just saved, override delete, call free internally}while(deathptr ! = this); }Copy the code

Refer to the article

Autorelease, autoreleasepool, autorelease, autoreleasepool