This post was originally posted on my blog

preface

Before you start, think about the following three scenarios. What are the outputs?

Pay attention tostrThe length should not be too short

Pay attention tostrThe length should not be too short

Pay attention tostrThe length should not be too short

@interface ViewController() { __weak NSString *string_weak; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Scenario 1 // NSString * STR = [NSString stringWithFormat:@"https://ityongzhen.github.io/"]; // string_weak = str; // Scenario 2 // @autoreleasepool {// NSString * STR = [NSString stringWithFormat:@"https://ityongzhen.github.io/"]; // string_weak = str; //} // // // NSString * STR = nil; @autoreleasepool { str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
        string_weak = str;
    }
    NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"string: %@ %s", string_weak,__func__);
}

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    NSLog(@"string: %@ %s", string_weak,__func__);
}
Copy the code

This problem, temporarily put down, continue to look.

autoreleasepoolGenerating c++ files

There is the following code


@autoreleasepool {
     NSObject *obj = [[NSObject alloc] init];
}

Copy the code

Run the xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m -o main-arm64. CPP command to generate a c++ file. The corresponding code is shown as follows.

{ __AtAutoreleasePool __autoreleasepool; 
        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

So this is going to be a simplification

{
 __AtAutoreleasePool __autoreleasepool; 
  NSObject *obj = [[NSObject alloc] init];
 }
Copy the code

What is __AtAutoreleasePool? This is a structure with the following contents, including a constructor, which is called when the structure is created. A destructor that is called when a structure is destroyed.

Struct __AtAutoreleasePool {// Constructor, called when the structure is created__AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } // The destructor is called when the structure is destroyed__AtAutoreleasePool(){
 	 objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  
  void * atautoreleasepoolobj;
};
Copy the code

So, putting it all together is objc_autoreleasePoolPush() at the beginning and objc_autoreleasePoolPop(AtAutoReleasepoolobj) at the end

Struct __AtAutoreleasePool {// Constructor, called when the structure is created__AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } // NSObject *obj = [[NSObject alloc] init]; // Destructor, which calls ~ when the structure is destroyed__AtAutoreleasePool(){
 	 objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  
  void * atautoreleasepoolobj;
};
Copy the code

Source code analysis

AutoreleasePoolPage

Objc_autoreleasePoolPush () and objc_autoreleasePoolPop can be found in the Runtime source code

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


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

That is, both functions are implemented using AutoreleasePoolPage.

The AutoreleasePoolPage class has many codes. The main codes are screened as follows

class AutoreleasePoolPage 
{

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
    
    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }

    bool lessThanHalfFull() {
        return(next - begin() < (end() - begin()) / 2); }... }Copy the code

It can be seen that

AutoreleasePoolPage objects are connected together in a bidirectional linked list

Among them

  • Magic is used to verify whether the structure of AutoreleasePoolPage is complete.
  • Next points to the next location of the newly added Autoreleased object, initialized to begin();
  • Thread refers to the current thread. AutoreleasePoolPage and thread one – to – one correspondence.
  • Parent points to the parent
  • Child refers to a child
  • Depth is the depth. It starts at 0 and increases by 1.
  • Hiwat stands for High Water Mark.

Each AutoreleasePoolPage object occupies 4096 bytes

  • Each AutoreleasePoolPage object takes up 4096 bytes of memory, except for its internal member variables and the remaining space for the address of the AutoRelease object
#define I386_PGBYTES 4096 /* bytes per 80386 page */

#define PAGE_SIZE I386_PGBYTES

static size_t const SIZE = PAGE_MAX_SIZE


  id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
 }
Copy the code

From the above source can be seen

  • eachAutoreleasePoolPageThere are 4096 bytes,
  • As well asbeginThe point is start storeAutorelease objectsPlace,
  • End points to the end

What if AutoreleasePoolPage cannot be saved?

If an AutoreleasePoolPage is no longer available, an AutoreleasePoolPage object is created. The child of the first AutoreleasePoolPage object points to the second AutoreleasePoolPage object. The parent of the second AutoreleasePoolPage object points to the first AutoreleasePoolPage object. The graph looks like this

push,pop,autorelease

AutoreleasePoolPage contains push and pop functions

  • Calling the push method pushes a POOL_BOUNDARY onto the stack and returns its memory address

  • The pop method is called with a POOL_BOUNDARY memory address, and a release message is sent from the last object pushed until the POOL_BOUNDARY is encountered

  • Id *next refers to the next area that can hold the address of an AutoRelease object

push

  static inline void *push() 
    {
        id *dest;
        if (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
 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if(page && ! Page ->full()) {// If the page is not full, add the obj object to the pagereturn page->add(obj);
        } else if(page) {//page full create a new pagereturn autoreleaseFullPage(obj, page);
        } else {
            returnautoreleaseNoPage(obj); }}Copy the code

pop

  static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        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 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 (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill(a);setHotPage(parent);
        } else if(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

autorelease

inline id 
objc_object::autorelease()
{
    if (isTaggedPointer()) return(id)this; / / call rootAutoreleaseif(fastpath(! ISA()->hasCustomRR()))return rootAutorelease();

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}
Copy the code

RootAutorelease call rootAutorelease2

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

    return rootAutorelease2();
}
Copy the code

RootAutorelease2 call autorelease

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2() { assert(! isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}
Copy the code

autorelease

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

POOL_BOUNDARY

POOL_BOUNDARY is an important role as a sentinel,

  • Every timeobjc_autoreleasePoolPushWhen called, runtime is directed to the currentAutoreleasePoolPageAdd a sentinel object (POOL_BOUNDARY) with a value of 0 (i.e. nil)
  • objc_autoreleasePoolPushThe return value of the sentinel object is the address of the sentinel object, byobjc_autoreleasePoolPop(Sentry object) as an input parameter, then
    • Find the page of the sentinel object based on the address of the sentinel object passed in
    • In the current page, all AutoRelease objects inserted after the sentinel object are sent once- releaseMessage, and moves the next pointer back to the correct position
    • Clean up from the most recently added object, spanning several pages to the page where the sentinel is

@autoreleasepoolThe nested

What if multiple @autoreleasepool are nested?

print

The source code has the following code

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


 static void printAll()
    {        
        _objc_inform("# # # # # # # # # # # # # #");
        _objc_inform("AUTORELEASE POOLS for thread %p", pthread_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(a); } } _objc_inform("# # # # # # # # # # # # # #");
    }
Copy the code

That is, the _objc_autoreleasePoolPrint function can be used to print some logs

A layer of@autoreleasepool

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {

        _objc_autoreleasePoolPrint();
        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

The output is as follows, with only one sentinel object (POOL)

objc[32644]: # # # # # # # # # # # # # #
objc[32644]: AUTORELEASE POOLS for thread 0x11b111d40
objc[32644]: 3 releases pending.
objc[32644]: [0x7fcabf802000]  ................  PAGE  (hot) (cold)
objc[32644]: [0x7fcabf802038]    0x600003f70500  __NSArrayI
objc[32644]: [0x7fcabf802040]    0x600000950f00  __NSSetI
objc[32644]: [0x7fcabf802048]  ################ POOL 0x7fcabf802048
objc[32644]: # # # # # # # # # # # # # #

Copy the code

Three layers@autoreleasepool

What if there are three @autoreleasepool?

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

If there are three pools, there are three sentinels.

objc[32735]: # # # # # # # # # # # # # #
objc[32735]: AUTORELEASE POOLS for thread 0x114f02d40
objc[32735]: 5 releases pending.
objc[32735]: [0x7f91fd005000]  ................  PAGE  (hot) (cold)
objc[32735]: [0x7f91fd005038]    0x600001bbd380  __NSArrayI
objc[32735]: [0x7f91fd005040]    0x600002da4eb0  __NSSetI
objc[32735]: [0x7f91fd005048]  ################ POOL 0x7f91fd005048
objc[32735]: [0x7f91fd005050]  ################ POOL 0x7f91fd005050
objc[32735]: [0x7f91fd005058]  ################ POOL 0x7f91fd005058
objc[32735]: # # # # # # # # # # # # # #

Copy the code

Destruction of a@autoreleasepool

What if the innermost @Autoreleasepool in the above code exits and then prints?

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

The output is as follows, with only two sentinels.

objc[32812]: # # # # # # # # # # # # # #
objc[32812]: AUTORELEASE POOLS for thread 0x1142b1d40
objc[32812]: 4 releases pending.
objc[32812]: [0x7fe86e800000]  ................  PAGE  (hot) (cold)
objc[32812]: [0x7fe86e800038]    0x600003edb0c0  __NSArrayI
objc[32812]: [0x7fe86e800040]    0x6000008dd680  __NSSetI
objc[32812]: [0x7fe86e800048]  ################ POOL 0x7fe86e800048
objc[32812]: [0x7fe86e800050]  ################ POOL 0x7fe86e800050
objc[32812]: # # # # # # # # # # # # # #
Copy the code

It further illustrates that,

  • Calling the push method pushes a POOL_BOUNDARY onto the stack and returns its memory address

  • The pop method is called with a POOL_BOUNDARY memory address, and a release message is sent from the last object pushed until the POOL_BOUNDARY is encountered

Opening question

Now let’s go back to the question at the beginning of the article. It should be easy to answer.

@interface ViewController() { __weak NSString *string_weak; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Scenario 1 // NSString * STR = [NSString stringWithFormat:@"https://ityongzhen.github.io/"]; // string_weak = str; // Scenario 2 // @autoreleasepool {// NSString * STR = [NSString stringWithFormat:@"https://ityongzhen.github.io/"]; // string_weak = str; //} // // // NSString * STR = nil; @autoreleasepool { str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
        string_weak = str;
    }
    NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"string: %@ %s", string_weak,__func__);
}

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    NSLog(@"string: %@ %s", string_weak,__func__);
}
Copy the code

The following output is displayed:

// Scenario 1 iOS timer [24714:332118] String: https://ityongzhen.github.io/ - [ViewController viewDidLoad] [24714-332118] string iOS timer: https://ityongzhen.github.io/ - [ViewController viewWillAppear:] iOS timer [24714-332118] string: (NULL) -[ViewController viewDidAppear:] scenario 2 iOS Timer [24676:331168] String: (NULL) -[ViewController viewDidLoad] iOS Timer [24676:331168] String: (NULL) -[ViewController viewWillAppear:] iOS Timer [24676:331168] String: (NULL) -[ViewController viewDidAppear:] iOS Timer [24505:328544] String: https://ityongzhen.github.io/ - [ViewController viewDidLoad] [24505-328544] string iOS timer: (NULL) -[ViewController viewWillAppear:] iOS Timer [24505:328544] String: (NULL) -[ViewController viewDidAppear:]Copy the code

The scene of a

When using [nsstrings stringWithFormat: @ “https://ityongzhen.github.io/”) to create an object, the object reference count is 1, This object is automatically added to the current AutoReleasepool. When the local variable STR is used to point to the object, the object’s reference count +1 becomes 2. Because under ARC NSString * STR is essentially __strong NSString * STR. So this object is always there until the viewDidLoad method returns with a reference count of 2. And when viewDidLoad returns, the local variable STR gets recycled and points to nil. Thus, the reference count of the object to which it points, -1, becomes 1.

In the viewWillAppear method, we can still print out the value of this object, but in the viewDidAppear method, it’s empty, and that’s where RunLoop comes in. RunLoop: RunLoop: RunLoop: RunLoop: RunLoop: RunLoop ViewDidLoad and viewWillAppear are in the same RunLoop, so in viewWillAppear, we can still print out the value of this object, but viewDidLoad, that RunLoop is done, The object is released completely.

Scenario 2

When using [nsstrings stringWithFormat: @ “https://ityongzhen.github.io/”) to create an object, the object reference count to 1. When the local variable STR is used to point to the object, the object’s reference count +1 becomes 2. When out of the current scope, the local variable STR becomes nil, so the reference count of the object it points to becomes 1. In addition, we know that when out of scope @Autoreleasepool {}, the current Autoreleasepool is drained and the autoreleased object is released. So the reference count for this object becomes zero, and the object is finally released

Scenario 3

When out of scope @Autoreleasepool {}, the autoreleased object is released and the reference count becomes 1. When STR goes out of the scope of the local variable STR, which is returned by the viewDidLoad method, STR points to nil, the reference count of the object it points to becomes zero, and the object is finally released

Pay attention to the point

I said be careful that the length of STR can’t be too short why?

Because if STR is too short. For example,

@interface ViewController() { __weak NSString *string_weak; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Scenario 1 // NSString * STR = [NSString stringWithFormat:@"abc"]; // string_weak = str; // Scenario 2 // @autoreleasepool {// NSString * STR = [NSString stringWithFormat:@"abc"]; // string_weak = str; //} // // // NSString * STR = nil; @autoreleasepool { str = [NSString stringWithFormat:@"abc"];
        string_weak = str;
    }
    NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"string: %@ %s", string_weak,__func__);
}

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    NSLog(@"string: %@ %s", string_weak,__func__);
}
Copy the code

The results are as follows:

// Scenario 1 iOS Timer [24714:332118] String: ABC -[ViewController viewDidLoad] iOS Timer [24714:332118] String: ABC -[ViewController viewWillAppear:] iOS timer [24714:332118] String: ABC -[ViewController viewDidAppear:] Scenario 2 iOS Timer [24676:331168] String: ABC -[ViewController viewDidLoad] iOS timer [24676:331168] String: ABC -[ViewController viewWillAppear:] iOS timer [24676:331168] String: ABC -[ViewController viewDidAppear:] iOS Timer [24505:328544] String: ABC -[ViewController viewDidLoad] iOS timer [24505:328544] String: ABC -[ViewController viewWillAppear:] iOS timer [24505:328544] String: ABC -[ViewController viewDidAppear:]Copy the code

This is because the string ABC uses Tagged Pointer technology and is not a standard OC object. There’s no such thing as creating space on the heap to store objects. For Tagged Pointer, please refer to the iOS reference count in this article.

conclusion

  • The main underlying data structures of the automatic release pool are __AtAutoreleasePool and AutoreleasePoolPage

  • Objects that call AutoRelease are ultimately managed through the AutoreleasePoolPage object

  • Each AutoreleasePoolPage object takes up 4096 bytes of memory, except for its internal member variables and the remaining space for the address of the AutoRelease object

  • All AutoreleasePoolPage objects are connected together in a bidirectional linked list

  • Calling the push method pushes a POOL_BOUNDARY onto the stack and returns its memory address

  • The pop method is called with a POOL_BOUNDARY memory address, and a release message is sent from the last object pushed until the POOL_BOUNDARY is encountered

  • Id *next refers to the next area that can hold the address of an AutoRelease object

  • IOS registers two observers in the Runloop of the main thread

    • 1.ObserverListen to thekCFRunLoopEntryEvent will be calledobjc_autoreleasePoolPush()
    • The secondObserver
      • Listen to thekCFRunLoopBeforeWaitingEvent will be calledobjc_autoreleasePoolPop(),objc_autoreleasePoolPush()
      • Listen to thekCFRunLoopBeforeExitEvent will be calledobjc_autoreleasePoolPop()
  • When the second RunLoop is about to end, call objc_autoreleasePoolPop().

The resources

Autorelease behind the black screen

The Runtime source

Objective-c Autorelease Pool

Detailed analysis of RunLoop source code

Basic principles of iOS

Reference counting in iOS

More information, welcome to pay attention to the individual public number, not regularly share a variety of technical articles.