1. Know before you explore

1.1 What are ARC and MRC?

I’m sure you’re familiar with this problem, but I’ll take you through it.

In the MRC era, the system determines whether an object is destroyed based on its reference counter. Where each object is created with a reference count of 1, and [obj retain] needs to be used manually whenever the object is referenced by another pointer. Make the object reference count +1, and manually release the object when the pointer variable is not in use. Set its reference count to -1, and when an object’s reference count is zero, the system destroys the object. In general, MRC mode must follow who creates, who releases, who references, and who manages

If ARC is used in the MRC mode, select the. M file to use the MRC mode from Build Phases Compile Sources, double-click the file, and enter -fobjc-arc in the displayed dialog box

ARC automatic memory management: WWDC2011 and iOS5 introduced automatic reference counting (ARC), which is not garbage collection but a feature of the compiler. The ARC management mechanism is similar to the MRC manual mechanism, except that manual calls to retain, release, and Autorelease are no longer required; When you use ARC, the compiler inserts release and autoRelease in place; The ARC era introduced strong references to replace retain and weak references. In summary ARC is the result of LLVM and Runtime

Using MRC in ARC: To use MRC in ARC, select the. M file to use MRC in Build Phases Compile Sources, double-click the file, and enter -fno-objc-arc in the displayed dialog box

1.2 autoreleasePool Automatic release pool

Autoreleasepool started in the ERA of MRC. It is mainly used to automatically perform the operation of reference counting -1 on the objects in the releasepool, that is, automatically execute the release method. In MRC, to use AutoReleasepool, you must manually call autoRelease for the object inside the code block to add the object to the automatic releasepool. The system automatically sends a release message to the object added to the auto-release pool after the code block ends. You don’t have to call release manually.

2. Preliminary study of autoreleasePool

2.1 Creating autoreleasePool

AutoreleasePool is created in the main function of the.m file created by default when we create the project, and we can also create autoreleasePool objects in our own code:

In main:

#import <UIKit/UIKit.h>

#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

In your own code:

- (void)testConst{
    
    @autoreleasepool {
        
        for (int i = 0; i<1000; i++) {
            NSString *str1 = [NSString stringWithFormat:@"%ld",i]; }}}Copy the code

In this code will create a large number of temporary variables (regardless of the code is not reasonable), will consume too much memory space, so in the development process, if you need to create and use a large number of temporary variables, You can put the relevant code in autoreleasePool. These temporary variables are automatically released when @Autoreleasepool {} is left.

2.2 Implementation of autoreleasePool

What about the underlying implementation of autoreleasePool?

LLVM source code analysis and clang-rewrite-objc compiler. Now let’s explore the second way:

M file clang-rewrite-objc-o main.cpp

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

We see that @Autoreleasepool {} underlying is compiled to __AtAutoreleasePool __autoreleasepool.

Let’s take a look at the __AtAutoreleasePool structure:

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

__AtAutoreleasePool is a structure containing:

__AtAutoreleasePool() {

     atautoreleasepoolobj = objc_autoreleasePoolPush();

} and

~__AtAutoreleasePool() {

   objc_autoreleasePoolPop(atautoreleasepoolobj);

__AtAutoreleasePool(){} is the constructor, while ~__AtAutoreleasePool() {} is the destructor, which is easier to understand when @autoreleasepool Atautoreleasepoolobj is destructed when the @AutoReleasepool {} method area is created.

But what exactly does objc_autoreleasePoolPush and objc_autoreleasePoolPop do? We can only find the answer from the source code, see the following analysis of the autoreleasePool progression.

3. Progression of autoreleasePool

Want to understand the underlying, must want to have the source code, first of all in our online http://www.opensource.apple.com/apsl/ a objc source through some configuration import our engineering. (the latest version is objc4 – version 779.1)

3.1 objc_autoreleasePoolPush

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

What is AutoreleasePoolPage? Click inside to see:

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
.
.
.
}

Copy the code

AutoreleasePoolPageData: AutoreleasePoolPageData: AutoreleasePoolPageData: AutoreleasePoolPageData

struct AutoreleasePoolPageData
{
	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

AutoreleasePoolPageData is a structure with its own member variables, such as magic_t structure variable, next pointer type variable, depth, hiwat, and parent and Child pointer member variables.

Uint32_t = uint32_t; uint32_t = uint32_t

static const uint32_t M0 = 0xA1A1A1A1;
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];
Copy the code

Int32_t M0 and size_t M1_len are 4 bytes each.

Static const, static variable. Static variable type data size is stored in memory in the global data segment, not in the heap,

So the size of magic_t structure is the size of uint32_t m[4], one uint32_t is four bytes, the whole is 4*4 is 16 bytes.

So the AutoreleasePoolPageData structure has 56 bytes of its own variables. The size of each variable has been marked above, where the meaning of each variable is as follows:


AutoreleasePoolPage is a bidirectional linked list. AutoreleasePoolPage is used to store autoReleases. However, the size of each page is limited. If an AutoreleasePoolPage has too many AutoReleases to store on one page, it needs to point to the parent node and continue to store on the AutoreleasePoolPage that points to the parent node.

So what’s the size of each page?

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;

public:
	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
}Copy the code

There’s PAGE_MAX_SIZE, click yes4096.It turns out that every pageAutoreleasePoolPageCan hold4096 bytes. It’s 4096 bytes,(4096-AutoreleasepoolPage bytes of its own member variable)/bytes of each object(4096-56)/8 = 505.Ok, every oneAutoreleasePoolPageIt can hold 505 objects. The following theAutoreleasePoolPage structurePut below, for your reference only.

Once the structure is clear, we continue with the code:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
Copy the 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

Else check if dest = autoreleaseFast(POOL_BOUNDARY); The POOL_BOUNDARY is the equivalent of an AutoreleasePoolPage boundary, also known as a sentinel object in some technical books. This is a very useful object, but we’ll see why in a moment:

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

As soon as I got into @Autoreleasepool {}, I called objc_autoreleasePoolPush, and then autoreleaseFast, AutoreleasePoolPage *page = hotPage(); AutoreleaseNoPage (obj); autoreleaseNoPage(obj);

id *autoreleaseNoPage(id obj) { ASSERT(! hotPage()); bool pushExtraBoundary =false;
        if (haveEmptyPoolPlaceholder()) {
            pushExtraBoundary = true;
        }
        else if(obj ! = POOL_BOUNDARY && DebugMissingPools) { _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         objc_thread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) {return setEmptyPoolPlaceholder();
        }
        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        return page->add(obj);
    }

Copy the code

Executing code discovery step by step leads to:

      // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        return page->add(obj);Copy the code

Create a New AutoreleasePoolPage and set thotPage, then add our sentinel object to the AutoreleasePoolPage. Is the page – > add (POOL_BOUNDARY); Operation.

3.2 Implementation of autoRelease method

@autoreleasepool {} will create a new AutoreleasePoolPage. This AutoreleasePoolPage will create POOL_BOUNDARY When will auto-free objects be added?

It must be that when an object calls the AutoRelease method,AutoreleasePoolPage adds the object called.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        NSObject *objc = [[NSObject alloc] autorelease];
        NSLog(@"objc = %@",objc);
}Copy the code

Back to the underlying objc:

objc_autorelease(id obj)
{
    if(! obj)return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}
Copy the code

objc_object::autorelease() { ASSERT(! isTaggedPointer());if(fastpath(! ISA()->hasCustomRR())) {return rootAutorelease();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
Copy the code

inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
Copy the code

objc_object::rootAutorelease2() { ASSERT(! isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}
Copy the code

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

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

AutoreleasePoolPage *page = hotPage(); Gets the page, which was created in 3.1, so it goes back to if (page &&! AutoreleasePoolPage ->add(obj) page->add(obj) If full, go to autoreleaseFullPage(obj, page);

id *autoreleaseFullPage(id obj, AutoreleasePoolPage *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

The first thing we get here is a do-while loop,while (page->full()); This method gets here because the previous page is full, so this condition must be satisfied. Then determine whether the page has a child node, if so, directly use the child node page, if not, you can only create a new page, and then the new page parent node points to the original node. The last page – > add (obj).

3.3 objc_autoreleasePoolPop(AtAutoReleasepoolobj)

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

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) {// First node - no parent nodeif(stop == page->begin() && ! page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold pagein 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

Inside this:

stop = (id *)token;
 if(*stop ! = POOL_BOUNDARY) { }Copy the code

*stop! *stop! *stop! *stop! Stop == POOL_BOUNDARY, stop == POOL_BOUNDARY,

Then popPage

(token, page, stop);

 popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            AutoreleasePoolPage *parent = page->parent;
            page->kill(a);setHotPage(parent);
        } else if(allowDebug && DebugMissingPools && page->empty() && ! page->parent) { // specialcase: delete everything for pop(top)
            // when debugging missing autorelease pools
            page->kill(a);setHotPage(nil);
        } else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill(a); }else if (page->child->child) {
                page->child->child->kill(a); }}}Copy the code

Will automatically release the pool page kill, including child page, parent page kill. Causes all Autorlease objects to be released.

4. To summarize

  • In the APP, the entire main thread runs in an auto-release pool.

  • The pool block provides a pop point that explicitly tells us that there is a release point, if your main has something else to put there during initialization.

  • Using the @Autoreleasepool tag, call the push() method.

  • Without hotpage, call () and set EMPTY_POOL_PLACEHOLDER.

  • Since EMPTY_POOL_PLACEHOLDER is set, this page will be set to hotpage, POOL_BOUNDARY will be added and obj will be added.

  • Continue with the object calling autoRelease, now that you have page, call Page ->add(obj).

  • If the page is full, call autoreleaseFullPage() to create a new page, repeating point 6.

  • When the autoreleasePool boundary is reached, the pop method is called, which normally releases all objects after POOL_BOUNDARY


  •