IOS memory management AutoreleasePool

_objc_autoreleasePoolPrint();

Build a function


AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    AutoreleasePoolPageData(begin(),
                            objc_thread_self(),
                            newParent,
                            newParent ? 1+newParent->depth : 0,
                            newParent ? newParent->hiwat : 0)
{ 
    if (parent) {
        parent->check();
        ASSERT(!parent->child);
        parent->unprotect();
        parent->child = this;
        parent->protect();
    }
    protect();
}


struct AutoreleasePoolPageData
{
    magic_t const magic;                // Check whether the structure is intact
    __unsafe_unretained id *next;       // The position next to the last object
    pthread_t const thread;             // The current thread
    AutoreleasePoolPage * const parent; / / the parent node
    AutoreleasePoolPage *child;         / / child nodes
    uint32_t const depth;               / / depth of 0 and 1
    uint32_t hiwat;

    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

Copy the code

AutoreleasePoolPageData is a bidirectional linked list.

Internal methods


id * begin() { // Start adding object positions
    return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() { // Table tail, the size of the table
    return (id *) ((uint8_t *)this+SIZE);
}

bool empty() { // table is not empty next == start
    return next = = begin();
}

bool full() { Next == end of table
    return next = = end();
}

bool lessThanHalfFull() { // Less than half?
    return (next - begin() < (end() - begin()) / 2);
}

id *add(id obj) // Add data to the table
{
    ASSERT(!full()); // The table is not full
    unprotect(); // It's like a lock
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj; // assign the position of obj to next
    protect();
    return ret;
}

void releaseAll() // Release all
{
    releaseUntil(begin());
}

void releaseUntil(id *stop) / / stop is the begin
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    
    // Loop from the last object to the first object
    while (this->next ! = stop) {
        // Restart from hotPage() every time, in case -release 
        // autoreleased more objects
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect(); // Similar to locking
        id obj = * --page->next; // Get the object obj at next
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // Set the memory next size to the next byte value to SCRIBBLE
        // static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
        page->protect(); // Similar to unlock

        if (obj ! = POOL_BOUNDARY) { //# define POOL_BOUNDARY nil sentry object, if obj is not sentry object, then release
            objc_release(obj);
        }
    }

    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        ASSERT(page->empty());
    }
#endif
}

static inline void setHotPage(AutoreleasePoolPage *page) 
{
    if (page) page->fastcheck();
    tls_set_direct(key, (void *)page);
}

Copy the code

operation


_objc_rootHash(id obj)
{
    return (uintptr_t)obj;
}

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

NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt); / / launch
}

void *
_objc_autoreleasePoolPush(void)
{
    return objc_autoreleasePoolPush();
}

void
_objc_autoreleasePoolPop(void *ctxt)
{
    objc_autoreleasePoolPop(ctxt);
}

void 
_objc_autoreleasePoolPrint(void)
{
    AutoreleasePoolPage::printAll(); // Print all
}

static inline id autorelease(id obj) // Automatically released objects
{
    ASSERT(obj); // The object has values
    ASSERT(!obj->isTaggedPointer()); // The object is not taggedPointer
    id *dest __unused = autoreleaseFast(obj); / / add
    ASSERT(!dest  ||  dest = = EMPTY_POOL_PLACEHOLDER  ||  *dest = = obj);
    return obj;
}

static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) { // Page is not full
        return page->add(obj); // page Adds an object
    } else if (page) {
        return autoreleaseFullPage(obj, page); / / page
    } else {
        return autoreleaseNoPage(obj); / / no page}}static inline void *push() 
{
    id *dest;
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY); // Create a pool from the sentinel object
    }
    ASSERT(dest = = EMPTY_POOL_PLACEHOLDER || *dest = = POOL_BOUNDARY);
    return dest;
}

static inline void
pop(void *token)
{
    AutoreleasePoolPage *page;
    id *stop;
    if (token = = (void*)EMPTY_POOL_PLACEHOLDER) {
        // Popping the top-level placeholder pool.
        page = hotPage();
        if (!page) {
            // Pool was never used. Clear the placeholder.
            return setHotPage(nil);
        }
        // Pool was used. Pop its contents normally.
        // Pool pages remain allocated for re-use as usual.
        page = coldPage();
        token = page->begin();
    } else {
        page = pageForPointer(token);
    }

    stop = (id *)token;
    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 {
            // Error. For bincompat purposes this is not 
            // fatal in executables built with old SDKs.
            returnbadPop(token); }}if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
        return popPageDebug(token, page, stop);
    }

    return popPage<false>(token, page, stop);
}

static void printAll()
{
    _objc_inform("# # # # # # # # # # # # # #");
    _objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());

    AutoreleasePoolPage *page;
    ptrdiff_t objects = 0;
    for (page = coldPage(); page; page = page->child) {
        objects + = page->next - page->begin();
    }
    _objc_inform("%llu releases pending.", (unsigned long long)objects);

    if (haveEmptyPoolPlaceholder()) {
        _objc_inform("[%p] ................ PAGE (placeholder)".EMPTY_POOL_PLACEHOLDER);
        _objc_inform("[%p] ################ POOL (placeholder)".EMPTY_POOL_PLACEHOLDER);
    }
    else {
        for (page = coldPage(); page; page = page->child) {
            page->print(a); } } _objc_inform("# # # # # # # # # # # # # #");
}

Copy the code

Push and POP are operations on page lists, and push creates a new table through a sentinel object (nil). Push at the beginning of the @Autoreleasepool scope and pop at the end.

When you add an object to AutoReleasepool, determine that the object has a value and is not a TaggedPoint object, and then call autoreleaseFast to determine whether the current page is full or empty to add the object to the Page.

When autoReleasepool fails, the first object is traversed from the last, and release is performed if it is not a nil sentinel object.

Autoreleasepool for main


int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

Copy the code

The comment tells us that the code to create the auto-free object is in here, and the pool should be if there’s some pre-initialized object that’s in here when it’s created, it can be destroyed later