Implementation principle of Objective-C Autorelease Pool

Source: Internet
Author: User

Implementation principle of Objective-C Autorelease Pool

Memory Management has always been one of the key and difficult points for learning Objective-C. Although it is already in the ARC era, it is still necessary to understand the memory management mechanism of Objective-C. Among them, figuring out the principles of autorelease is even more important. Only by understanding the principles of autorelease can we truly understand the Objective-C memory management mechanism. Note: The runtime source code used in this article is the latest version.objc4-646.tar.gz.

When will the autoreleased object be released?

Autorelease is essentially a delay in calling release. When will the autoreleased object be released? To solve this problem, we will first make a small experiment. This small experiment is divided into three scenarios. Please first think about console output in each scenario to deepen your understanding. Note: the source code of this experiment can be found in AutoreleasePool.

12345678910111213141516171819202122232425262728293031323334
_ Weak NSString * string_weak _ = nil;-(void) viewDidLoad {[super viewDidLoad]; // scenario 1 NSString * string = [NSString stringWithFormat: @ leichunfeng]; string_weak _ = string; // scenario 2 // @ autoreleasepool {// NSString * string = [NSString stringWithFormat: @ leichunfeng]; // string_weak _ = string; //} // scenario 3 // NSString * string = nil; // @ autoreleasepool {// string = [NSString stringWithFormat: @ leichunfeng]; // string_weak _ = string; //} NSLog (@ string: % @, string_weak _);}-(void) viewWillAppear :( BOOL) animated {[super viewWillAppear: animated]; NSLog (@ string: % @, string_weak _);}-(void) viewDidAppear :( BOOL) animated {[super viewDidAppear: animated]; NSLog (@ string: % @, string_weak _);}

How did you think? I believe you have an answer in your mind. Let's take a look at the console output:

1234567891011121314
// Scenario 12015-05-30 10:32:20. 837 AutoreleasePool [33876: 1448343] string: leichunfeng2015-05-30 10:32:20. 838 AutoreleasePool [33876: 1448343] string: leichunfeng2015-05-30 10:32:20. 845 AutoreleasePool [33876: 1448343] string: (null) // scenario 22015-05-30 10:32:50. 548 AutoreleasePool [33915: 1448912] string: (null) 10:32:50. 549 AutoreleasePool [33915: 1448912] string: (null) 10:32:50. 555 AutoreleasePool [33915: 1448912] string: (null) // scenario 32015-05-30 10:33:07. 075 AutoreleasePool [33984: 1449418] string: leichunfeng2015-05-30 10:33:07. 075 AutoreleasePool [33984: 1449418] string: (null) 10:33:07. 094 AutoreleasePool [33984: 1449418] string: (null)

Are there any differences with the expected results? Any way. Let's take a look at why we get this result.

Analysis: In three scenarios, we use[NSString stringWithFormat:@leichunfeng]An autoreleased object is created, which is the prerequisite for our experiment. In addition, in orderviewWillAppearAndviewDidAppearTo continue accessing this object, we use a global__weakVariablestring_weak_To point to it. Because__weakOne feature of a variable is that it does not affect the lifecycle of the object to which it points. Here we use this feature.

Scenario 1: When used[NSString stringWithFormat:@leichunfeng]When an object is created, the reference count of this object is 1, and the object is automatically added to the current autoreleasepool. When using local variablesstringWhen pointing to this object, the reference count of this object + 1 is changed to 2. Because under ARCNSString *stringEssentially__strong NSString *string. ThereforeviewDidLoadBefore the method is returned, this object always exists and the reference count is 2. WhenviewDidLoadLocal variables when the method returnsstringRecycled, pointingnil. Therefore, the reference count of the object to which it points is-1, which is 1.

InviewWillAppearMethod, we can still print the value of this object, indicating that this object has not been released. Isn't that scientific? If I read less books, you lie to me. Isn't it always said that when the function returns, the objects generated inside the function will be released? If you think like this, I can only say: Sao Nian, you have been so old. Just kidding. Let's continue. As mentioned above, this object is an autoreleased object. The autoreleased object is added to the latest autoreleasepool. Only when the autoreleasepool is drain, the autoreleased object in the autoreleasepool will be release.

In addition, we note that whenviewDidAppearWhen this object is printed, the object value becomesnilThe object has been released. Therefore, we can make a bold guess that this object must be inviewWillAppearAndviewDidAppearMethods are released at some time, and because the autoreleasepool in which it is located is released when it is drain.

What are you talking about? You can prove to me that your mom is your mom. Well, I can't prove it, but I can still prove it from the above guesses. Don't believe it, you see!

Before we start, I will briefly describe the principle. We can uselldbOfwatchpointCommand to set observation points and observe global variablesstring_weak_,string_weak_The variable stores the address of the autoreleased object we created. Here, we use__weakAnother feature of a variable is that when the object it points to is released,__weakThe variable value is setnil. After understanding the basic principles, we began to verify the above guesses.

We will first open a breakpoint in line 3. When the program runs to this breakpoint, we willlldbCommandwatchpoint set v string_weak_Set observation points and observestring_weak_Variable value changes. As shown in, we will see similar output in the console, indicating that we have successfully set an observation point:

After setting the observation point, clickContinue program executionTo continue running the program, we will see the interface shown in:

Let's take a look at the output in the console.string_weak_The variable value is composed0x00007f9b886567d0Changed0x0000000000000000, That isnil. This indicates that the object it points to is released. In addition, we can also note that the value of the object is printed twice on the console.viewWillAppearIt has also been called, andviewDidAppearNot called yet.

Next, let's look at the thread stack on the left. We see a very sensitive method call.-[NSAutoreleasePool release], This method is finally calledAutoreleasePoolPage::pop(void *)Function to perform the release operation on the autoreleased object in the autoreleasepool. Based on the previous analysis, we know thatviewDidLoadThe autoreleased object created in this operation has a reference count of 1 after the method is returned. Therefore, after the release operation, the reference count of this object-1 is changed to 0, the autoreleased object is finally released, and you can guess it.

In addition, we did not manually add autoreleasepool in the Code. Where did the autoreleasepool come from? After reading the subsequent chapters, you will understand.

Scenario 2: Similarly, when[NSString stringWithFormat:@leichunfeng]When an object is created, the reference count of this object is 1. When using local variablesstringWhen pointing to this object, the reference count of this object + 1 is changed to 2. When the current scope exists, local variablesstringChangednilSo the reference count of the object to which it points is 1. In addition, we know that@autoreleasepool {}The current autoreleasepool is drain, and the autoreleased object is release. Therefore, the reference count of this object is 0, and the object is finally released.

Scenario 3: Similarly, when@autoreleasepool {}The autoreleased object is release, and the reference count of the object is changed to 1. When a local variable is generatedstringScope, that isviewDidLoadWhen the method returns,stringPointednilThe reference count of the object to which it points is 0, and the object is finally released.

Understanding when the autoreleased object is released in these three scenarios is very helpful for us to understand the Objective-C memory management mechanism. Among them, scenario 1 appears most frequently, that is, we do not need to manually add@autoreleasepool {}Directly use the autoreleasepool maintained by the system. Scenario 2 requires manual addition.@autoreleasepool {}Manually intervene in the release time of the autoreleased object. Scenario 3 is introduced to distinguish scenario 2. In this scenario@autoreleasepool {}Scope when the autoreleased object is released.

PS: Please refer to the analysis process in scenario 1 and uselldbCommandwatchpointVerify the release time of the autoreleased object in scenario 2 and scenario 3 by yourself. you should give it a try yourself.

AutoreleasePoolPage

Careful readers should have noticed that we have mentioned above-[NSAutoreleasePool release]The method is ultimately calledAutoreleasePoolPage::pop(void *)Function to perform the release operation on the autoreleased object in the autoreleasepool.

What is AutoreleasePoolPage? In fact, autoreleasepool does not have a separate memory structure. It is implemented through a bidirectional linked list with AutoreleasePoolPage as the node. Open the source code project of runtimeNSObject.mmThe source code of autoreleasepool can be found in lines 438th-932 of the file. By reading the source code, we can know:

  • The autoreleasepool of each thread is actually a pointer stack;
  • Each pointer represents a release object or POOL_SENTINEL (The Sentinel object represents the boundary of an autoreleasepool );
  • A pool token is the memory address of the POOL_SENTINEL corresponding to the pool. When the pool is pop, all objects whose memory addresses are after the pool token will be release;
  • This stack is divided into a page-based two-way linked list. Pages will be dynamically added or deleted when necessary;
  • Thread-local storage points to hot page, that is, the page where the newly added autoreleased object is located.

    Shows the memory structure of an empty AutoreleasePoolPage:

    1. magicUsed to verify whether the structure of AutoreleasePoolPage is complete;
    2. nextPoints to the next location of the newly added autoreleased object. Pointsbegin();
    3. threadPoint to the current thread;
    4. parentPoint to the parent node. The parent value of the first node isnil;
    5. childPoint to the child node. The child value of the last node isnil;
    6. depthIndicates the depth, starting from 0 and increasing by 1;
    7. hiwatHigh water mark.

      In addition, whennext == begin()The AutoreleasePoolPage is empty. Whennext == end()The AutoreleasePoolPage is full.

      Autorelease Pool Blocks

      We useclang -rewrite-objcCommand to rewrite the following Objective-C code to C ++ code:

      123
      @autoreleasepool {}

      The following output result is returned (only the relevant code is retained ):

      123456789101112
      extern C __declspec(dllimport) void * objc_autoreleasePoolPush(void);extern C __declspec(dllimport) void objc_autoreleasePoolPop(void *);struct __AtAutoreleasePool {  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}  void * atautoreleasepoolobj;};/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;}

      I have to say that Apple@autoreleasepool {}The implementation is really clever. It can really be called the art of code. Apple declares__AtAutoreleasePoolType local variable__autoreleasepoolTo achieve@autoreleasepool {}. When declared__autoreleasepoolVariable, constructor__AtAutoreleasePool()Called, that is, executionatautoreleasepoolobj = objc_autoreleasePoolPush();; When the current scope is available, the destructor~__AtAutoreleasePool()Called, that is, executionobjc_autoreleasePoolPop(atautoreleasepoolobj);. That is to say@autoreleasepool {}The implementation code can be further simplified as follows:

      12345
      /* @ Autoreleasepool */{void * atautoreleasepoolobj = callback (); // The user code. All objects that receive the autorelease message will be added to the autoreleasepool vertex (atorauteleasepoolobj );}

      Therefore, the running process of a single autoreleasepool can be simply understoodobjc_autoreleasePoolPush(),[Object autorelease]Andobjc_autoreleasePoolPop(void *)Three processes.

      Push operation

      As mentioned aboveobjc_autoreleasePoolPush()A function is essentially a push function called by AutoreleasePoolPage.

      123456
      void *objc_autoreleasePoolPush(void){    if (UseGC) return nil;    return AutoreleasePoolPage::push();}

      Therefore, let's take a look at the role and execution process of the push function of AutoreleasePoolPage. A push operation is actually to create a new autoreleasepool. The specific implementation of AutoreleasePoolPage isnextInsert a POOL_SENTINEL location and return the memory address of the inserted POOL_SENTINEL. This address is the pool token we mentioned earlier. It serves as the input parameter of the function when performing the pop operation.

      123456
      static inline void *push(){    id *dest = autoreleaseFast(POOL_SENTINEL);    assert(*dest == POOL_SENTINEL);    return dest;}

      Call the push functionautoreleaseFastFunction to perform specific insert operations.

      1234567891011
      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);    }}

      autoreleaseFastWhen executing a specific insert operation, the function processes three different situations:

      1. When the current page exists and is not full, add the object to the current page directly, that isnextPoint to the location;
      2. When the current page exists and is full, create a new page and add the object to the newly created page;
      3. When the current page does not exist, that is, when there is no page, create the first page and add the object to the newly created page.

        Each time a push operation is called, a new autoreleasepool is created, that is, a POOL_SENTINEL is inserted into the AutoreleasePoolPage, and the memory address of the inserted POOL_SENTINEL is returned.

        Autorelease operation

        PassNSObject.mmSource file, we can find-autoreleaseMethod implementation:

        123
        - (id)autorelease {    return ((id)self)->rootAutorelease();}

        View((id)self)->rootAutorelease()Method call, we found that the final call is the AutoreleasePoolPageautoreleaseFunction.

        1234567
        __attribute__((noinline,used))idobjc_object::rootAutorelease2(){    assert(!isTaggedPointer());    return AutoreleasePoolPage::autorelease((id)this);}

        AutoreleasePoolPageautoreleaseThe implementation of the function is more capacity-oriented for us. It is very similar to the implementation of the push operation. The push operation inserts a POOL_SENTINEL, while the autorelease operation inserts a specific autoreleased object.

        12345678
        static inline id autorelease(id obj){    assert(obj);    assert(!obj->isTaggedPointer());    id *dest __unused = autoreleaseFast(obj);    assert(!dest  ||  *dest == obj);    return obj;}
        Pop operation

        Likewise,objc_autoreleasePoolPop(void *)Functions are essentially called by AutoreleasePoolPage.popFunction.

        12345678910
        voidobjc_autoreleasePoolPop(void *ctxt){    if (UseGC) return;    // fixme rdar://9167170    if (!ctxt) return;    AutoreleasePoolPage::pop(ctxt);}

        The input parameter of the pop function is the return value of the push function, that is, the memory address of POOL_SENTINEL, that is, the pool token. When the pop operation is performed, all autoreleased objects whose memory address is after the pool token will be release. Until the page where the pool token is locatednextPoint to pool token.

        The following is the memory structure of a thread's autoreleasepool stack. In this autoreleasepool stack, there are two POOL_SENTINEL, that is, two autoreleasepools. The stack consists of three AutoreleasePoolPage nodes. The first AutoreleasePoolPage node iscoldPage(), The Last AutoreleasePoolPage node ishotPage(). The first two nodes are full, and the last node stores the newly added autoreleased object.objr3Memory Address.

        In this case, ifpop(token1)Operation, the memory structure of the autoreleasepool stack will be changed to as shown in:

        NSThread, nsunloop, and nstutoreleasepool

        According to the description of the nsunloop in Apple's official documentation, we can know that every thread, including the main thread, will have an exclusive nsunloop object and will be automatically created when necessary.

        Each NSThread object, including the application's main thread, has an nsunloop object automatically created for it as needed.

        Similarly, according to the description of the NSAID utoreleasepool in Apple's official documents, we can see that the nsunloop object in the main thread should be the same in other threads at the system level, for example through dispatch_get_global_queue (dispatch_queue_prior) before each event loop starts, the system automatically creates an autoreleasepool and drain at the end of the event loop. The autoreleased object created in scenario 1 above is added to the autoreleasepool automatically created by the system, and is released when the autoreleasepool is drain.

        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.

        Additionally, each thread maintains its own autoreleasepool stack. In other words, autoreleasepool is closely related to threads. Each autoreleasepool corresponds to only one thread.

        Each thread (including the main thread) maintains its own stack of ntutoreleasepool objects.

        Understanding the relationship between NSThread, nsunloop, and nstutoreleasepool helps us understand the memory management mechanism of Objective-C as a whole and what the system has done for us, understand the entire operating mechanism.

        Summary

        Here, I believe you should have a better understanding of the Objective-C memory management mechanism. In general, we do not need to manually add autoreleasepool, so we can use the autoreleasepool automatically maintained by the thread. According to the description of Using Autorelease Pool Blocks in Apple's official documentation, we know that in the following three cases, we need to manually add autoreleasepool:

        1. If the program you write is not based on the UI framework, such as the command line tool;
        2. If a large number of temporary objects are created in the loop you write;
        3. If you create a secondary thread.

          Finally, I hope this article will help you, have fun!

           

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.