preface

In previous articles, I talked about memory layout in OC, memory management schemes, and retainCount, retain, and release in MRC, but MRC is a thing of the past, so let’s talk about ARC. The change from MRC to ARC depends on @Autoreleasepool.

@autoreleasepool Automatic releasepool: A pool that manages memory. Objects that are not needed are placed in the automatic releasepool, and objects in this pool are automatically released (delayed).

Application scenarios of @autoreleasepool:

  1. When there are a large number of temporary variables
  2. Non-ui operations, such as command lines
  3. Create your own helper threads

Where to begin

AutoreleasePool is created and released

We can make a breakpoint anywhere in the project Po [NSRunLoop currentRunLoop]

  • 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. The above is from the elder brother

See @autoreleasepool{} compiled into C++ code

Use the compiler clang to compile main.m into main. CPP (at the terminal: clang-rewrite-objc main.m)

$CD main.m Folder

$ clang -rewrite-objc main.m -o main.cpp

This will generate a main. CPP in the original path, and when clicked, we can find the following code:

__AtAutoreleasePool

Note: here~Destructor is the C++ destructor, which, in contrast to the constructor, is automatically executed when an object is out of its scope (for example, when the function in which the object resides has been called).

Objc_autoreleasePoolPush objc_autoreleasePoolPop objc_autoreleasePoolPop objc_autoreleasePoolPush objc_autoreleasePoolPop objc_autoreleasePoolPop

@autoreleasepool pushes code in its scope (that is, “{}”) and pops it out when it’s done.

Into the source code

Let’s take a look at this objc_autoreleasePoolPush

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

An AutoreleasePoolPage appears, and the syntax is C++ syntax. Let’s take a look at what this AutoreleasePoolPage is.

AutoreleasePoolPage

Let’s look at variable declarations first

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
#elsePAGE_MAX_SIZE; // 4096 bytes size and alignment, power of 2#endifstatic size_t const COUNT = SIZE / sizeof(id); magic_t const magic; // 4*4 bytes to verify the structure of AutoreleasePoolPage is complete; id *next; // 8 bytes point to the next location of the newly added Autoreleased object, initialized to begin(); pthread_t const thread; // 8 bytes point to the current thread; AutoreleasePoolPage * const parent; // 8 bytes pointing to the parent node, the first node's parent value is nil; AutoreleasePoolPage *child; //8 bytes pointing to the child, the last child is nil; Uint32_t const depth; // 4 bytes, representing the depth, starting from 0 and incrementing by 1; uint32_t hiwat; // 4 bytes, representing high water mark. // This adds up to 56 bytes... }Copy the code

void
_objc_autoreleasePoolPrint(void); 
Copy the code

AutoreleasePoolPage size = 4096; AutoreleasePoolPage size = 4096; AutoreleasePoolPage size = 4096; AutoreleasePoolPage size = 4096; The size of an NSObject is 8 bytes, which means that an AutoreleasePoolPage has room for 4040/8=505 NSObject pairs. So there’s a wave of this

void
_objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (int i = 0 ; i<505; i++) {
            NSObject *obj = [[NSObject new]autorelease];
        }
        _objc_autoreleasePoolPrint();
     }
    return 0;
}
Copy the code

Then look at the printed message

Omit hundreds of NSObject…

  • The first PAGE is marked “full” (cold). The first word is easy to understand. The second word is basically “cold”.
  • Hot is the opposite of cold. Hot is hot. In short, the first PAGE is full and marked inactive, and the second PAGE is not full and active.

So the question is, according to our previous calculation, 505 should not be just enough to fill a PAGE? What the hell is this second page? Obviously there’s something weird going on. Let’s look at these three addresses

objc[32613]: [0x102002000] ……………. PAGE (full) (cold) objc[32613]: [0x102002038] ################ POOL 0x102002038 objc[32613]: [0x102002040] 0x100f8d780 NSObject

The difference between the first two addresses is 38, which is in hexadecimal notation, so 38 is 3*16+8 = 56 in decimal notation, so the number of # is the starting point for the variable to enter the pool. The third address is 40, and 38 is 40. Let’s make the 3 into the 4. So it’s something in 0x102002038. And what is this? I’m going to post some code here

    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 yetassert(! hotPage());// pushExtraBoundary to push an additional boundary
        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // When pushing a new page or the first page, a poolboundary needs to be pushed before pushing.
            pushExtraBoundary = true;
        }
        else if(obj ! = POOL_BOUNDARY && DebugMissingPools) {// Exception condition, throw error
            _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 pool in 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);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {  
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);
    }
Copy the code

So just to read this code here when we push an object in and there’s no page or the page is full, and we need to go to a new page, it’s going to set pushExtraBoundary to true, and at the bottom it’s going to say pushExtraBoundary, and if it’s true, A POOL_BOUNDARY is pushed into the page first.

So the POOL_BOUNDARY is the “strange thing” we mentioned above. From the literal translation we can understand that it is a “boundary” or “boundary character”.

Conclusion:

  1. Autoreleasepool consists of numerous AutoreleasepoolPages
  2. When an AutoreleasePoolPage is full, a new AutoreleasePoolPage is created, and the two pages are associated with each other by parent/ Child
  3. After 56 bytes of AutoreleasePoolPage’s own variable, POOL_BOUNDARY is pushed when the push object enters the page. This boundary character also takes 8 bytes

Here is a picture to better understand and deepen the impression

That’s where this article ends. The next autoReleasepool will parse push and pop