A,autoreleasepoolconcept

Autoreleasepool essentially delays the release of an object. After an object is used, it is not released immediately. Instead, it is added to the releasepool and released at a suitable time.

The main thread auto-release pool is described in the official documentation as follows:

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

Second,ARCwithMRCUnder theautoreleasepoolThe difference between

In MRC, you need to manually manage the creation and release of automatic release pools. In ARC, you just need to use @autoreleasepool to include the corresponding code.


- (void)MRCTest {

    Person *person;
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    person = [[Person alloc] initWithName:@"jam" age:24];
    [person autorelease];
    NSLog(@"before pool release person: %@", person);
    [pool release];
    NSLog(@"after pool release person: %@", person); //crash } before pool release person: name:jam, age:24
crash ...

- (void)ARCTest {
    Person *person;
    @autoreleasepool {
    person = [[Person alloc] initWithName:@"jam" age:24];
    NSLog(@"before end release pool person: %@", person);
    }
    NSLog(@"after end release pool person: %@", person); } before end release pool person: name:jam, age:24
after end release pool person: name:jam, age:24
Copy the code

According to the log output, MRC will release the autoRelease object after calling the automatic release pool release method. Therefore, the person variable accessed later is a wild pointer, and accessing it again will naturally cause crash. In ARC, @Autoreleasepool does not release the Person variable immediately after the closing parenthesis character, but at an appropriate point in time. And when that happens, we’ll talk about that.

Ps: x-code specifies how to use MRC for certain files: -fno-objc-arc

Three,autoreleasepoolwithrunloopThe relationship between

For breakpoint debugging, use Po [NSRunLoop currentLoop]

By above knowable, automatic release pool is registered in the runloop two observer, respectively with _wrapRunLoopWithAutoreleasePoolHandler callback. However, there are some differences between activities and orders in the two observers.

A. First of all, look at the differences between activities.

typedef CF_OPTIONS(CFOptionFlags.CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

The activities of the first observer are 0x01 (kCFRunLoopEntry), the activities of the second observer are 0xA0 (converted to binary 10100000), Namely kCFRunLoopBeforeWaiting | kCFRunLoopExit.

B. The difference between the two orders, where order represents the priority of runloop execution events.

order = - 2147483647.
order = 2147483647

int32 max: 2147483647
int32 min: - 2147483648.
Copy the code

According to the comparison of activities and order above, it can be learned that:

The first Observer has a priority of -2147483647 (highest priority) when runloop listens on kCFRunLoopEntry, which guarantees that the Observer callback will occur before any other event callback.

The second observer the runloop listeners kCFRunLoopBeforeWaiting | kCFRunLoopExit priority for 2147483647, which ensure that the observer callback in other events will happen after the callback

What did the two observers do to the auto-release pool during their respective callbacks? Let’s look at a little example

Person *p;
// Here is the break point
p = [[Person alloc] initWithName:@"jam" age:24];
NSLog(@"p: %@", p);
Copy the code

The watchpoint set variable p command is used to monitor the change of variable p. The watchpoint set variable p command is used to monitor the change of variable P. The watchpoint command is used to monitor the change of variable P.

CoreFoundation`objc_autoreleasePoolPush:
-> 0x107e6a2fc <+0>: jmpq *0x1e88d6(%rip) ; (void *)0x000000010a9bd50f: objc_autoreleasePoolPush

CoreFoundation`objc_autoreleasePoolPop:
-> 0x107e6a2f6 <+0>: jmpq *0x1e88d4(%rip) ; (void *)0x000000010a9bd5b3: objc_autoreleasePoolPop
Copy the code

These two sections are obviously related to the autofree pool, and correspond to the push and POP operations of the free pool, respectively, which are actually related calls after callbacks through the above two observers. (There’s really no good evidence for the connection, just speculation based on the above example.)

Therefore, when runloop into kCFRunLoopEntry, automatic release pool for a push operation, when the runloop into kCFRunLoopBeforeWaiting | kCFRunLoopExit state, automatic release pool for pop operation. Each runloop iteration includes automatic release pools push and POP.

Four,@autoreleasepoolThe principle of

Find out by rewriting the main.m file to a CPP file using the CLang compiler.

clang -rewrite-objc main.m
Copy the code

Fatal error: ‘UIKit/ uikit. h’ file not found fatal error: ‘UIKit/ uikit. h’ file not found

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
Copy the code

The -isysroot option is used to specify the SDK directory to be used for compilation, i.e. the SDK directory under X-code.

//.m
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);
}

//.cpp
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
Copy the code

As you can see, the generated CPP file adds a variable to the __AtAutoreleasePool structure

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

From the definition of this structure, you can see that the objc_autoreleasePoolPush() method is called at initialization, and when its destructor, the instance of the structure is destroyed, The objc_autoreleasePoolPop(AtAutoReleasepoolobj) method is called.

Five,objc_autoreleasePoolPushandobjc_autoreleasePoolPopThe principle of

Both runloop and @autorelesepool will end up in these two methods, so let’s take a look at the source code to see what they do. (Ps: You can download the compiled Runtime source code here.)

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

NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
Copy the code

Based on the code above, you can see that the push and POP operations call the class methods of AutoreleasePoolPage, respectively. Let’s look at the definition of AutoreleasePoolPage:

class AutoreleasePoolPage : privateAutoreleasePoolPageData {... }struct AutoreleasePoolPageData
{
magic_t const magic; // Check the integrity check
__unsafe_unretained id *next; 
pthread_t const thread; // The current thread
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
Copy the code

Here are some noteworthy:

A. Parent and child variables form a bidirectional linked list

The next variable is used as the next point to the newly added AutoRelease object and is stored as a stack

Autorelease pool data structure as shown above: double linked list + stack

With the structure of AutoreleasePoolPage understood, let’s take a closer look at the push and POP operations, respectively

pushoperation

static inline void *push(a) 
{
    id *dest;
    if (slowpath(DebugPoolAllocation)) { // A new page is generated directly in debug mode
    // 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;
}

#define POOL_BOUNDARY nil
Copy the code

Depending on whether you are in Debug mode or not, you can ignore the debug mode for the moment, which is to call the autoreleaseFast method, pass in a nil object, and finally return the Dest object as the return value of the push method.


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 {
    returnautoreleaseNoPage(obj); }}Copy the code

A. First, it retrieves the current page using the hotPage method. If the page exists and there is not enough space, add obj to the page.

B. If the page exists but the space is full, create a sub-page to store obJ

C. If the page does not exist, create a new page to store obJ

  • The currentpageThe current page here refers toAutoreleasePoolPageThe current node page in the linked list.
/ / get the page
static inline AutoreleasePoolPage *hotPage(a) 
{
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
    tls_get_direct(key);
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    if (result) result->fastcheck();
    return result;
}

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

/ / AutoreleasePoolPage statement
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
Copy the code

You can see that they call the tls_get_direct and tls_set_direct methods to read and store the page, respectively.


static inline void *tls_get_direct(tls_key_t k)
{ 
    ASSERT(is_valid_direct_key(k));
    if (_pthread_has_direct_tsd()) {
    return _pthread_getspecific_direct(k);
    } else {
    returnpthread_getspecific(k); }}static inline void tls_set_direct(tls_key_t k, void *value) 
{ 
    ASSERT(is_valid_direct_key(k));
    if (_pthread_has_direct_tsd()) {
    _pthread_setspecific_direct(k, value);
    } else{ pthread_setspecific(k, value); }}Copy the code

The TLS(Thread Local Storage) Thread Local variable is used to store the page, that is, the Local Storage space of the current Thread is used to store the page. In this way, the association between the Thread and the automatic release pool is realized. The automatic release pool of different threads is independent of each other.

  • Handling of insufficient page space
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;
    else page = new AutoreleasePoolPage(page);
    } while (page->full());
    setHotPage(page);
    return page->add(obj);
}

Copy the code

As above, if the current page is running out of space, iterate through it until it finds a page with space. If it finds none, create a child page and update the current page node so that it can be added next time (without traversing the search).

  • Page does not exist
static __attribute__((noinline))
    ....
    // 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

As above, if the page does not exist, a new page is created (as the head node of the linked list) and updated to TLS.

  • addAction: In either case, this is called at the endaddMethod to add an object to the correspondingpageIn the
id *add(id obj)
{ ASSERT(! full()); unprotect(); id *ret = next;// faster than `return next-1` because of aliasing
    *next++ = obj;
    protect();
    return ret;
}
Copy the code

As mentioned above, *next is the location of the newly added object, so here we assign *next to the current object and move it to the next location.

  • autoreleaseFastMethod call

A. AutoreleasePoolPage:push method, passed POOL_BOUNDARY(nil) object

When the push method is called, a nil object is passed as a “sentinel object” to identify the range of objects added between each push and pop, so that when the POP operation is performed, the corresponding object is released exactly (up to the “sentinel” position).

As above, objects of Obj2-5 are freed when pop is performed.

B. AutoreleasePoolPage: autorelease method, was introduced into the actual object obj

static inline id autorelease(id obj)
{ ASSERT(obj); ASSERT(! obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);return obj;
}
Copy the code

Under ARC, the compiler inserts the AutoRelease method in place. Therefore, objects are automatically added to the auto-release pool.

popoperation

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);
}
Copy the code
  1. The token passed in here is the pointer to the sentinel object returned by the push operation.

  2. EMPTY_POOL_PLACEHOLDER is an optimization for only 1 pool, so you can leave this detail out for now.

  3. Get the current to the page using the pageForPointer method

  4. if (*stop ! = POOL_BOUNDARY), from the first point above, you can know that token should be p after operation, return “sentinel” object, if not, then exception processing.

  • Gets the location of the Sentinel objectpage
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;
}
Copy the code

Since the SIZE of each page is fixed, the offset can be obtained by p % SIZE, and then the starting address of the page can be obtained by p-offset.

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

ReleaseUntil method is used to release the object. After release, the object will be adjusted according to the space of the page. The first two ifs are in debug mode, which can be ignored for now.

void releaseUntil(id *stop) 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    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();
    id obj = *--page->next;
    memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
    page->protect();

    if(obj ! = POOL_BOUNDARY) { 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
}
Copy the code

We use a while loop to iterate from the current page until next points to Stop.

  1. Gets the current page, because if there are multiple pages, the page will be traversed along the list
  2. If the current page is empty, it moves forward and updates the current page
  3. Gets the current object that needs to be released, and sets the location toSCRIBBLE.nextThe needle moves forward.
  4. Finally, if the current object is not a Sentinel object, the object is released

The specific process is shown in the figure below:

Vi.autoreleasepoolwithNSThreadThe relationship between

The relationship between the two mainly involves two points:

A. Autoreleasepool relies on TLS for the current thread.

B. The creation and release of autoreleasepool in different threads

  • In the main thread, the system has passed main.m@autoreleasepoolAutomatic release pools have been created, so we don’t need to create and release any more
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
  • So in the child thread, do we need to use, like the main thread@autoreleasepoolMethod to create and release?

In ARC, we know that the compiler automatically inserts the autoRelease method in the appropriate place, and we mentioned above that the autoreleaseFast method is also called when the AutoRelease method is called when we analyzed the push operation. Therefore, the automatic releasepool will be added to autoReleasepool regardless of whether we manually create it or not.

NSObject *obj = [[NSObject alloc] init];

// After compiling:
NSObject *obj = [[NSObject alloc] init];
[obj autorelease];

static inline id autorelease(id obj)
{ ASSERT(obj); ASSERT(! obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);return obj;
}
Copy the code

Now that the automatic release pool is created, let’s look at its release operation. We know that @Autoreleasepool in the main thread is released using the objc_autoreleasePoolPop method. How do you release a child thread that does not call such a method? Let’s start with an example:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
    [thread start];
}

- (void)threadRun {
    Person *p = [[Person alloc] initWithName:@"jam" age:24 date:[NSDate date]];
    self.person = p; // Here is the break point
    NSLog(@"run in %@"[NSThread currentThread]);
}
Copy the code

Set watchpoint to variable p at self.person = p and continue executing until the thread finishes executing.

Click in to see how the call starts:

_pthread_tsd_cleanup

void
_pthread_tsd_cleanup(pthread_t self)
{
	int i, j;
	void *param;
	for (j = 0;  j < PTHREAD_DESTRUCTOR_ITERATIONS;  j++)
	{
		for (i = 0;  i < _POSIX_THREAD_KEYS_MAX;  i++)
		{
			if (_pthread_keys[i].created && (param = self->tsd[i]))
			{
				self->tsd[i] = (void *)NULL;
				if (_pthread_keys[i].destructor)
				{
					(_pthread_keys[i].destructor)(param);
				}
			}
		}
	}
}
Copy the code

Obviously, this function cleans up the TLS resource of the current thread, traverses all pthread_key_t, and calls its destructor. We know that autoreleasepool has a corresponding pthread_KEY_t in the thread

static pthread_key_t const key = AUTORELEASE_POOL_KEY;

static void init(a)
{
    int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                         AutoreleasePoolPage::tls_dealloc);
    ASSERT(r == 0);
}

static void tls_dealloc(void *p) 
{
    if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
        // No objects or pool pages to clean up here.
        return;
    }

    // reinstate TLS value while we work
    setHotPage((AutoreleasePoolPage *)p);

    if (AutoreleasePoolPage *page = coldPage()) {
        if(! page->empty()) objc_autoreleasePoolPop(page->begin());// pop all of the pools
        if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
            // pop() killed the pages already
        } else {
            page->kill();  // free all of the pages}}// clear TLS value so TLS destruction doesn't loop
    setHotPage(nil);
}
Copy the code

Therefore, the automatic release pool in the child thread is created and released without any additional action on our part. Of course, in some scenarios, you can also create and release them manually via @Autoreleasepool.

Seven,autoreleasepoolwithenumerateObjectsUsingBlock

EnumerateObjectsUsingBlock method will automatically add a @ autoreleasepool internally, in order to make sure the next iteration before removal of temporary objects, thus reducing memory peaks.

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        NSArray *arr = @[@"str1"The @"str2"The @"str3"]; [arr enumerateObjectsUsingBlock: ^ (id _Nonnull obj, NSUInteger independence idx, BOOL * _Nonnull stop) {id o = obj; / / here to set breakpoints NSLog (@"obj: %@", o);
        }];
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code

Set a breakpoint at id o = obj, add the watchpoint set variable O, and run the program. At the end of each iteration, releaseUnitl will be called:

The relevant data

  • Using Autorelease Pool

  • NSAutoReleasePool

  • autoreleasepool and nsthread

  • Autorelease behind the black screen