directory

1. The nature of autoRelease

2. Autoreleasepool source code parsing

3. Structure of autoreleasePoolPage

4. Structure and working principle of autoreleasePool

5. Autoreleasepool nested

6. The relationship between autorelaeasepool, NSRunLoop and child threads


1. The nature of the autorelease

  • Autorelease essentially delays calling the release method

In MRC, you can delay memory release by using [obj autoRelease]

2. Autoreleasepool source code parsing

The project main.m file is displayed

int main(int argc, char * argv[]) {
    @autoreleasepool {
        //ARC automatically adds the autoRelease method
        NSObject * obj = [[NSObject alloc] init];
        / / / MRC writing
        // NSObject * obj = [[[NSObject alloc] init] autorelease];}}Copy the code

Go to the terminal, CD to the main.m file directory, and run the following command to convert the file to main. CPP to view the underlying invocation method: In the main. CPP file, the main function is converted to the following C code:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; /// objc_autoreleasePoolPush is called
        NSObject * obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); }}Copy the code

__AtAutoreleasePool is a C structure

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

There are two main methods:

1. __AtAutoreleasePool() { atautoreleasepoolobj =objc_autoreleasePoolPush(); }: constructor, called when the structure is created2. ~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); }: destructor, called when the structure is destroyedCopy the code
  • For convenience, convert the code to the following pseudocode:
int main (int argc, char * argv[]) {
// push 
void *poolToken =objc_autoreleasePoolPush(); This is written in {.} code/ / pop will {... } objects all perform a release operationObjc_autoreleasePoolPop (Sentinel object address);// The sentinel object will be covered later
}
Copy the code

– objc_autoreleasePoolPush() and objc_autoreleasePoolPop(atAutoReleasepoolobj) are two methods:

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

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

AutoreleasePool relies on AutoreleasePoolPage. AutoreleasePool relies on AutoreleasePoolPage.


3. AutoreleasePoolPage structure

The following are the main parts of the AutoreleasePoolPage structure

Class AutoreleasePoolPage {PAGE_MAX_SIZE; // Max size 4096 bytes magic_t const magic; AutoreleasePoolPage id *next; // Points to the location where the next autoreleased object will be stored (when next == begin(), AutoreleasePoolPage is empty; If next == end(), AutoreleasePoolPage is full of pthread_t const thread; // An AutoreleasePoolPage can correspond to only one thread, but one thread can correspond to multiple AutoReleasepoolPages. AutoreleasePoolPage * const parent; // point to the parent, the first parent is nil; AutoreleasePoolPage *child; // points to a child, and the last child is nil; uint32_t const depth; The depth of the first page is 0, and the depth of each subsequent page is increased by 1. }Copy the code

The diagram below:

  • 1.AutoreleasePoolPage is essentially a node object with a size of 4096 words (PAGE_MAX_SIZE: 4096).
  • 2. The first seven variables are all 8 bytes, and the remaining 4040 bytes hold the address of the AutoRelease object
  • 4. Call analysis of push
// Sentry object is a nil object defined by a macro
# define POOL_BOUNDARY  nil

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

static inline void *push()
{
    return autoreleaseFast(POOL_BOUNDARY);
}

static inline id *autoreleaseFast(id obj)
{
   //hotPage() indicates the AutoreleasePoolPage node of the current page
    AutoreleasePoolPage *page = hotPage(); 
    if (page && !page->full()) {
     // If the current page exists and is not full, add the object directly to the current page, where next points to
        return page->add(obj); 
    } else if (page) { 
       // If the current page exists and is full, create a new page and add objects to the newly created page
        return autoreleaseFullPage(obj, page); 
    } else {
        // If the current page does not exist, i.e. there is no page, create the first page and add the object to the newly created page
        returnautoreleaseNoPage(obj); }} [obj autoRelease], add the autoRelease method to the object, in fact, directly call autoreleaseFastCopy the code

4. Structure and working principle of autoreleasePool

  • Autoreleasepool is essentially a stack of Pointers. The internal structure consists of a number of bidirectional linked lists with AutoreleasePoolPage objects as nodes. The system dynamically adds or removes page nodes as needed. AutoreleasePoolPage is a bidirectional linked list.

  • Referring to the figure above, the whole process looks like this:

1. Before running the loop, the system automatically creates an Autoreleasepool (there are multiple AutoreleasepoolPages in an Autoreleasepool). Then the objc_autoreleasePoolPush function is called once. The Runtime adds a POOL_BOUNDARY to the current AutoreleasePoolPage and returns the memory address of the sentinel.

2. The next pointer points to the address following the POOL_BOUNDARY (object address 1).

If the ARC compiler automatically inserts an autoRelease into the object, it will be added to the AutoreleasePoolPage at the same location as the next pointer, for example, next points to address 1. This is the address of the object after add in 1 object address here, and then pointing to the object of next address 2, and so on, each adding an address will move forward, until to the end () says it has to fill.

4. When an object is created, the AutoreleasePoolPage stores the address of the object until it is full, and a new AutoreleasePoolPage is created, using the child pointer and the parent pointer to the next and previous page, thus creating a bidirectional linked list. The order in which object addresses are stored is shown.

5. When objc_autoreleasePoolPop is called, if we add the last object address 8 as shown in the figure above, then the object address 8 -> object address 1 will be called release for each object. Until the sentinel object address is encountered.

  • Call timing of _objc_autoreleasePoolPush() and _objc_autoreleasePoolPop

The main thread RunLoop registers two observers after the App starts: _warpRunLoopWithAutoreleasePoolHandle () the first Entry, the observer to monitor events callback invocation _objc_autoreleasePoolPush () to create large pools of automatic release, highest priority, Ensure that the release pool is created before any other callbacks. The second observer listens for two events: 1.Before Waiting calls to _objc_autoreleasePoolPop and push empty the automatic release pool. 2. On Exit, call _objc_autoreleasePoolPop torelease the information. The lowest priority ensures that its release pool occurs after all other callbacks.


5. Autoreleasepool nested

What happens when multiple AutoReleasepool objects are nested and freed? Each time a new @Autoreleasepool is created, a push operation is performed. This is implemented by inserting a POOL_BOUNDARY into the next position in the AutoreleasePoolPage. As follows:

@autoreleasepool   {//autoreleasepool1
       NSObject * obj1 = [[NSObject alloc] init];
   
    @autoreleasepool  {//autoreleasepool2
        NSObject * obj2 = [[NSObject alloc] init];
        NSObject * obj3 = [[NSObject alloc] init]; }}Copy the code

Release process:

  1. When AutoReleasepool1 is created, sentinel object 1 is added, followed by the creation of obj1, where the address of obj1 is added.

  2. When AutoReleasepool2 is created, the sentry object 2 is added after obj1 (the next pointer above points to the principle), and then obj2 and obj3 are added in sequence.

  3. When autoReleasepool2 ends, obj3, obj2, will find the nearest AutoReleasepool that’s autoReleasepool2, and then call release in turn, all the way to the sentry object 2.

  4. When AutoReleasepool1 ends, when Obj1 calls release until sentinel object 1,


6. The relationship between autorelaeasepool, NSRunLoop and child threads

  1. The main thread enables Runloop by default. The Runloop automatically creates Autoreleasepool for us and performs Push, Pop, and other operations for memory management.

  2. Runloop is not enabled by default for child threads. When an Autorelease object is generated, it is added to the last created Autoreleasepool, usually the autoreleasepool in the main function, which is managed by the main thread runloop. Instead of manually creating an Autoreleasepool, thread destruction releases the object in the last Autoreleasepool created.

  3. You need to manually create automatic release pools for user-defined NSOperation and NSThread. For example, automatic release pools must be added to the main method of the custom NSOperation class. Otherwise, when the autofree object is out of scope, it will leak memory because there is no autofree pool to process it. However, for the default operations, blockOperation and invocationOperation, the system has already wrapped them for us. There is no need to manually create automatic release pools.

  4. AutoreleasePool is thread-by-thread (the thread pointer in the structure points to the current thread), and there is an AutoreleasePool for each thread opened.

Just give it a thumbs up and go