1. Automatic release pool

1. Related concepts

  1. It is generally OK to increment the reference count of an object at the beginning of a function or method and to decrease the reference count of an object when the function or method does not need it.

  2. Problem: Some functions or methods need to return an object, and the system may destroy the object before it can be returned. To ensure that the object returned by a function or method is not destroyed before it is returned, NSAutoreleasePool is used.

  3. Automatic release pool means that it is a container (collection) of objects, and automatic release pool guarantees delayed destruction of all objects in the pool. For auto-release pool purposes, all objects should be added to the auto-release pool so that the auto-release pool destroys all objects in the pool before destroying them.

  4. Autorelease method. This method does not change the object’s reference count, it just adds the object to the automatic release pool. This method returns the object on which it was called.

  5. When a program calls the autorelease method of an object on the autorelease pool described below, the method simply adds the object to the autorelease pool, which causes all objects in the pool to execute the release method when the autorelease pool is released.

  6. The autoloquy pool destruction is the same as that of any normal object. The autoloquy pool object is destroyed automatically as long as its reference count is 0. All objects in the pool are reclaimed when NSAotoreleasePool’s dealloc method is called.

  7. NSAutoreleasePool also provides a drain method to destroy objects in the autorelease pool. Unlike Release, release causes the reference count of the auto-release pool itself to be 0, allowing the system to reclaim the NSAutoreleasePool object, and all objects in the pool are reclaimed before the NSAutoreleasePool object is reclaimed. The drain method simply reclaims all objects in the pool and does not destroy the auto-release pool.

2. Run the logic

AutoReleasePool is an automatic memory reclamation mechanism for OC, which delays the release of variables added to AutoReleasePool. Under normal circumstances, created variables are released when they are out of scope, but if you add variables to AutoreleasePool, release is delayed and will not be released immediately if they are out of scope. Runloop is not released until it goes to sleep or is out of AutoReleasePool scope.

Auto release pool works by:

  1. The main thread corresponds to the program starting until loading is completeRunloopIn adormancyStatus until the user clicks interactive wake upRunloop
  2. Each user interaction starts onceRunloopUsed to handle user click, interaction events
  3. RunloopWhen awakened, it willAutoReleasePool is automatically createdAnd adds all deferred release objects toAutoReleasePool
  4. In a completeRunloopBefore the execution is complete, the system automatically sends a message to theAutoReleasePoolObject sending inNews releaseAnd thenDestruction of AutoReleasePool

The operation mechanism and relationship between AutoreleasePool and Runloop will be explained later in Runloop.

3. Effect analysis

Here is an example to illustrate the role of automatic release pools. We create strings in one of two ways:

NSString * string1 = [[NSString alloc] initWithFormat:@" Hello world..."] ; NSString * string2 = [NSString stringWithFormat:@" Hello world Auto relase..."] ;Copy the code

So what’s the difference between these two approaches? We understand its internal implementation through assembly:

  • Method 1

    NSString * string1 = [[NSString alloc] initWithFormat:@"hello world..."] ;Copy the code

    useallocOut of the way the string is calledrelease(assuming the string is not referenced by anything else, the variable will go out of scoperelease).

  • Way 2

    NSString * string2 = [NSString stringWithFormat:@"hello world auto relase..."] ;Copy the code

    usestringWithThe way the string is inapiThe inside is going to be set toautoreleaseInstead of manually releasing, the system will recycle, so it will be in the nearest automatic release pooldrainorreleaseWhen it is recycled.

Here is a case study to understand the role of automatic release pools. In the case, a string is created in two ways and assigned to an __weak decorated member variable.

  • Scenario 1

    __weak NSString *weakSrting; __weak NSString *weakSrtingAutoRelease; @implementation ViewController - (void)createStringFunc {NSString * string1 = [[NSString alloc] initWithFormat:@"hello world..."] ; weakSrting = string1; NSString * string2 = [NSString stringWithFormat:@" Hello world Auto relase..."] ; weakSrtingAutoRelease = string2; } - (void)viewDidLoad { [super viewDidLoad]; [self createStringFunc]; NSLog(@"weakSrting: %@", weakSrting); NSLog(@"weakSrtingAutoRelease: %@", weakSrtingAutoRelease); } - (void)viewWillAppear:(BOOL)animated { NSLog(@"view will appear weakSrting: %@", weakSrting); NSLog(@"view will appear weakSrtingAutoRelease: %@", weakSrtingAutoRelease); } - (void) viewDidAppear:(BOOL)animated { NSLog(@"view did appear weakSrting: %@", weakSrting); NSLog(@"view did appear weakSrtingAutoRelease: %@", weakSrtingAutoRelease); }Copy the code

    View the run result:

    WeakSrting, a string created using method 1, is released when createStringFunc is executed (end of scope). Weakreference weakSrting is also released. So weakSrting printing results are empty.

    WeakSrtingAutoRelease is an object created in Mode 2. This object is automatically added to the current AutoReleasepool to delay the release. This object is an Autoreleased object. The autoreleased object was added to the most recent Autoreleasepool, and only when the autoreleasepool itself drains, Autoreleased objects in autoReleasepool are released.

    WeakSrtingAutoRelease, which prints the object when it is printed in viewDidAppear, indicating that the object has not been released yet. The object must have been released sometime between the viewWillAppear and viewDidAppear methods, and it was released when the AutoReleasepool it was in was released. WeakSrtingAutoRelease We can set watchpoint (watchpoint set V weakSrtingAutoRelease) in LLDB debugging to see the object release process:

    As you can see in the run stack, the weakSrtingAutoRelease object completes the release when the automatic release pool is released.

  • Scenario 2 (ARC)

    This scheme is more intuitive. An @AutoReleasepool is manually added in the code. WeakSrtingAutoRelease will not be released in the automatic releasepool, but will be released when it is out of the automatic releasepool. See below:

    However, the object weakSrtingAutoRelease created in mode 2 can be used normally in the automatic release pool, and it will be released out of the automatic release pool, playing the effect of delayed release.

    But the use ofMethod 1String createdweakSrtingWhy was it released in the automatic release pool? He’s not gonna join the auto-release pool? This problem will be explained below!!

2. Principle analysis of automatic release pool

1. Preliminary study on principle

The implementation principle of automatic release pool is shown in clang below:

When compiled, @Autoreleasepool becomes the following code:

    __AtAutoreleasePool __autoreleasepool;
Copy the code

Global search for the __AtAutoreleasePool definition to find the __AtAutoreleasePool structure definition:

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

This structure provides a constructor, objc_autoreleasePoolPush, and a destructor, objc_autoreleasePoolPop. So the autoreleasepoolpool is essentially a structure that creates the autoreleasepoolPool through objc_autoreleasePoolPush, which releases the autoreleasepoolPOP.

By setting a symbolic breakpoint and viewing the assembly, you can determine the source code for the auto-release pool implementation in the libobjC.a. dylib library, which is most familiar to us.

2. Structural analysis

The following analysis through the source code. Trace the objc_autoreleasePoolPush method implementation as shown below:

It calls the objc_autoreleasePoolPush() method to continue tracing the code:

In the implementation of this method, it calls the Push method of AutoreleasePoolPage. So what is the structure of AutoreleasePoolPage? See below:

The AutoreleasePoolPage class comments provide the following key information:

  • A thread’s auto-release pool is a bunch of Pointers
  • Each pointer is either an object to be released orPOOL_BOUNDARY(Auto release pool boundary – Sentinel object)
  • The stack is split into oneA list of bidirectional linked pagesObjects are added to the page and removed as needed
  • Thread local storage points to newly automatically freed hot page objects that are stored

What to make of the above comment information? AutoreleasePoolPageData implementation

class AutoreleasePoolPage; struct AutoreleasePoolPageData { #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS struct AutoreleasePoolEntry { uintptr_t ptr: 48. uintptr_t count: 16; static const uintptr_t maxCount = 65535; // 2^ 16-1}; static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!" ); #endif magic_t const magic; // 16 __unsafe_unretained id *next; // 8 pthread_t const thread; // 8 AutoreleasePoolPage * const parent; // 8 AutoreleasePoolPage *child; // 8 uint32_t const depth; // 4 uint32_t hiwat; // 4 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

Attribute description:

  • magicUsed to checkAutoreleasePoolPageWhether the structure is complete;
  • nextPoints to the latest additionautoreleasedObject at the next location to which it points when initializedbegin() 
  • threadPoint to current thread
  • parentThat points to the parent, the first nodeparentA value ofnil
  • childThat points to the child, the last nodechildA value ofnil
  • depthIs for depth, from0Start and increase1
  • hiwatOn behalf ofhigh water markMaximum number of pushes flag

3. Source code implementation

Tracking push implementation source code:

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);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
Copy the code

In non-debug mode, the autoreleaseFast method is first called and the boundary object (sentinel object) is passed in. AutoreleaseFast implementation source code

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();

        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }
Copy the code

In this method, the current hotPage is first retrieved, and if it is not empty and not full, obj is added to the page; If the page is full, the autoreleaseFullPage method is called; If the current hotPage does not exist, that is, there is no page, the autoreleaseNoPage method is called. AutoreleaseNoPage implementation source code as follows:

After AutoreleasePoolPage is created, add the sentinel object first, and then add the obj. First look at the AutoreleasePoolPage constructor, as shown below:

Initialization is done by calling the constructor of AutoreleasePoolPageData and determining the linked list relationships between the pages. The AutoreleasePoolPageData attribute is 56 bytes. See below:

Because the next field in the page is used to set where the OBj is stored, and because each page has some properties that take up some space, the starting value of next is the 56 bytes shift at the beginning of the page, as determined by the begin() method in the constructor.

If the page is full, the autoreleaseFullPage method above is called, as shown in the implementation source code below:

AutoreleasePoolPage = AutoreleasePoolPage = AutoreleasePoolPage

  • AutoreleasepoolIs composed of multipleAutoreleasePoolPageIn the form of a bidirectional linked list
  • AutoreleasepoolThe basic principle of the automatic release pool: when created, will be in the currentAutoreleasePoolPageSets a marker bit (boundary) during which an object is calledautoreleaseObject is added toAutoreleasePoolPageIn the
  • If the current page is full, a new page is initialized, which is then linked in a bidirectional list, and the initialized page is set tohotPageWhen automatically released from the poolpopWhen, from the bottom uppop, calling each object’sreleaseMethod until the flag bit is encountered

4. Full page threshold

How many objects can an autofree pool store on a page? It would be much easier to understand the auto-release pool if we could print out the auto-release pool data. The source code also provides methods for printing data structures:

    void 
    _objc_autoreleasePoolPrint(void)
    {
        AutoreleasePoolPage::printAll();
    }

    __attribute__((noinline, cold))
    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();
            }
        }
        _objc_inform("##############");
    }
Copy the code

Take a look at its internal storage structure with the following example:

As can be seen from the output above, the start page of the auto release pool is 0x10380a000, the address shift is 56 bytes, and then the sentry object is placed, and the address of the sentry object is 0x10380A038, followed by four objects. So how many can fit on one page? There is also a definition in the source code:

static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif

#define PAGE_MIN_SHIFT          12
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)
Copy the code

Through the interpretation of the source code can be determined, its size is 1<<12, that is, 4096, and each page of its own attributes occupy 56 bytes, while the first page needs a sentinel object 8 bytes, so the home page can put (4096-56-8) / 8 = 504 objects. Verify:

As you can see from the output auto-release pool data structure, when 505 objects are put in, a new page is opened and there is only one object in the second page. (Sentinel objects will only be placed on the first page) so the first page can hold up to 504 objects, and then 505 objects per page.

3. Automatically release the pool

1. Object release instead of destruction

Take a look at the following example:

When the auto-release pool ends, instead of destroying the object, simply send a release message to the object stored in the auto-release pool.

2. Automatically release the nesting of the pool

Automatic release pools can be nested!

As you can see from this example, the automatic release of pool nesting does not affect the data structure, but just inserts one more sentinel object.

3. Which objects can be added to the automatic release pool

Still through the case analysis.

  • MRC environment

  • The ARC environment

Autorelase (alloc, init,copy, etc.); autorelase (alloc, init,copy, etc.);

2. StringWithFormt, which by its name is not held by the caller, is either automatically added to the auto-release pool or is a constant string that is not managed by reference counting.