Memory management has always been one of the key points and difficulties in learning objective-c, although it is now the ARC era, it is still necessary to understand Objective-c's memory management mechanism. Among them, to clarify the principle of autorelease is the most serious, only to understand the principle of autorelease, we really understand the objective-c memory management mechanism. Note: The runtime source code used in this article is currently the latest version of objc4-646.tar.gz.
When will the Autoreleased object be released?
Autorelease is essentially delaying call release, and when will the Autoreleased object be released? In order to understand the problem, let's do a little experiment first. This little experiment is done in 3 different scenarios, so you can think about the console output in each scenario to deepen your understanding. Note: The source code of this experiment can be found here autoreleasepool.
__weak NSString * string_weak_ = nil;
-(void) viewDidLoad {
[super viewDidLoad];
// scene 1
NSString * string = [NSString stringWithFormat: @ "leichunfeng"];
string_weak_ = string;
// Scene 2
// @autoreleasepool {
// NSString * string = [NSString stringWithFormat: @ "leichunfeng"];
// string_weak_ = string;
//}
// Scene 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's the thinking going? Believe in your heart already have the answer. So let's take a look at the console output:
// scene 1
2015-05-30 10: 32: 20.837 AutoreleasePool [33876: 1448343] string: leichunfeng
2015-05-30 10: 32: 20.838 AutoreleasePool [33876: 1448343] string: leichunfeng
2015-05-30 10: 32: 20.845 AutoreleasePool [33876: 1448343] string: (null)
// Scene 2
2015-05-30 10: 32: 50.548 AutoreleasePool [33915: 1448912] string: (null)
2015-05-30 10: 32: 50.549 AutoreleasePool [33915: 1448912] string: (null)
2015-05-30 10: 32: 50.555 AutoreleasePool [33915: 1448912] string: (null)
// Scene 3
2015-05-30 10: 33: 07.075 AutoreleasePool [33984: 1449418] string: leichunfeng
2015-05-30 10: 33: 07.075 AutoreleasePool [33984: 1449418] string: (null)
2015-05-30 10: 33: 07.094 AutoreleasePool [33984: 1449418] string: (null)
Is there any discrepancy with the results you expected? Any way, let's analyze why we have this result.
Analysis:in 3 scenarios, we have created a autoreleased object through [nsstring stringwithformat:@ "Leichunfeng"], which is the premise of our experiment. And, in order to continue accessing this object in Viewwillappear and Viewdidappear, we used a global __weak variable string_weak_ to point to it. Because the __weak variable has an attribute that it does not affect the life cycle of the object being pointed at, here we take advantage of this feature.
Scenario 1: When an object is created with [NSString stringwithformat:@ "Leichunfeng"], the reference count for this object is 1, and the object is automatically added to the current Autoreleasepool In When a local variable string is used to point to this object, the reference count of this object is +1, which becomes 2. Because under ARC nsstring *string is essentially __strong nsstring *string. So before the Viewdidload method returns, this object is always present and the reference count is 2. When the Viewdidload method returns, the local variable string is recycled, pointing to nil. Therefore, the reference count of the object to which it is pointing-1, becomes 1.
In the Viewwillappear method, we can still print out the value of this object, indicating that the object has not been released. It's not science, is it? I read less, you cheat me. Not all the time. When the function returns, will the object generated inside the function be released? If you think so, then I can only say: "You are too old." A joke, we go on. As we mentioned earlier, this object is a Autoreleased object, and the Autoreleased object is added to the current autoreleasepool, only when the autoreleasepool itself is drain, The Autoreleased object in Autoreleasepool will be release.
In addition, we notice that when the object is printed again in Viewdidappear, the object's value becomes nil, indicating that the object has been disposed of at this time. Therefore, we can boldly speculate that this object must have been released at some point between the Viewwillappear and Viewdidappear methods, and that it was released when the Autoreleasepool was drain.
What are you talking about? You can prove to me that your mother is your mother. Well, I really can't prove that, but the above speculation I can still prove, do not believe, you see!
Before I start, let me briefly explain the principle that we can use Lldb's Watchpoint command to set the observer, to observe the change in the value of the global variable String_weak_, and the String_weak_ variable to save the autoreleased we created. The address of the object. Here again we take advantage of another feature of the __weak variable, that is, when the object it points to is freed, the value of the __weak variable is set to nil. Having learned the fundamentals, we begin to verify the above guesses.
We first hit a breakpoint in line 35th, and when the program runs to this breakpoint, we set the observation point with the LLDB command Watchpoint set v String_weak_ and observe the change in the value of the STRING_WEAK_ variable. As shown, we will see similar output in the console stating that we have successfully set up an observation point:
After setting the observation point, click the Continue Program execution button and continue running the application, we will see the interface as shown:
Let's look at the output in the console and notice that the value of the STRING_WEAK_ variable is changed from 0x00007f9b886567d0 to 0x0000000000000000, which is nil. Indicates that the object it is pointing to is now disposed. In addition, we can also notice a detail, that is, the console printed two times the value of the object, indicating that Viewwillappear has been called, and Viewdidappear has not been called.
Next, let's take a look at the thread stack on the left. We see a very sensitive method called-[nsautoreleasepool release], and this method eventually calls Autoreleasepoolpage::p the OP (void *) function to be responsible for the Autoreleasepool in the The Autoreleased object performs a release operation. In conjunction with the previous analysis, we know that the Autoreleased object created in Viewdidload has a reference count of 1 after the method returns, so after the release operation here, the object's reference count-1, becomes 0, and the Autoreleased object is eventually To release, to be guessed.
Also, it is worth mentioning that we do not manually add Autoreleasepool in the code, then where is this autoreleasepool? After reading the following chapters you will understand.
Scenario 2: Similarly, when an object is created by [NSString stringwithformat:@ "Leichunfeng"], the reference count for this object is 1. When a local variable string is used to point to this object, the reference count of this object is +1, which becomes 2. When the current scope is out, the local variable string becomes nil, so the reference count of the object it points to becomes 1. In addition, we know that when the scope of the @autoreleasepool {} is out, the current autoreleasepool is drain, where the Autoreleased object is release. So the reference count of this object becomes 0, and the object is eventually released.
Scenario 3: Similarly, when the scope of the @autoreleasepool {} is out, the Autoreleased object is release, and the reference count of the object becomes 1. When the scope of the local variable string is returned, that is, when the Viewdidload method returns, the string points to nil, and the reference count of the object that it points to becomes 0, and the object is eventually disposed.
It is very helpful to understand when autoreleased objects are released in these 3 scenarios for our understanding of OBJECTIVE-C's memory management mechanism. Of these, Scene 1 appears most, that is, we do not need to manually add @autoreleasepool {}, the direct use of system maintenance Autoreleasepool; Scenario 2 is the case that requires us to manually add @autoreleasepool {}, manual intervention When the Autoreleased object is released, Scene 3 is introduced in order to distinguish scene 2, in which case the object is freed when the scope of the @autoreleasepool {} is not reached.
PS: Please refer to the analysis process of scenario 1, using the LLDB command Watchpoint self-verification under scene 2 and Scene 3 Autoreleased object release time, you should give it a try Yoursel F.
Autoreleasepoolpage
The attentive reader should have been aware that the-[nsautoreleasepool release] method we have mentioned above is ultimately responsible for calling the Autoreleasepoolpage::p op (void *) function to The Autoreleased object in Autoreleasepool performs the release operation.
So what's the autoreleasepoolpage here? In fact, Autoreleasepool is not a separate memory structure, it is through the autoreleasepoolpage as a node of the two-way linked list to achieve. We open the runtime of the source project, in the nsobject.mm file 第438-932 line can find Autoreleasepool implementation of the source code. By reading the source code, we can know:
-
The autoreleasepool of each thread is actually a stack of pointers;
-
Each pointer represents an object that needs to be release or Pool_sentinel (Sentinel object, which represents a autoreleasepool boundary);
-
A pool token is the memory address of the pool_sentinel that the pool corresponds to. When the pool is pop, all memory addresses after the pool token will be release;
-
This stack is divided into a doubly linked list with page nodes. Pages are dynamically added or deleted when necessary;
-
Thread-local Storage (thread-local storage) points to the hot page, which is the page that contains the newly added Autoreleased object.
An empty autoreleasepoolpage memory structure as shown in:
-
Magic is used to verify the integrity of the autoreleasepoolpage structure;
-
Next points to the next position of the newly added Autoreleased object, which points to begin () when initialized;
-
Thread points to the current threads;
-
The parent points to the parents node, and the parent value of the first node is nil;
-
Child points to sub-nodes, and the last node has a value of nil;
-
The depth represents the depth, starting from 0 and incrementing by 1;
-
Hiwat represents high water mark.
Also, when next = = Begin (), the autoreleasepoolpage is empty, and when next = = End (), the autoreleasepoolpage is full.
Autorelease Pool Blocks
We use the CLANG-REWRITE-OBJC command to rewrite the following objective-c code into C + + code:
@autoreleasepool {}
The following output will be obtained (only the relevant code is retained):
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;
}
It has to be said that Apple's implementation of the @autoreleasepool {} is really ingenious and can really be called the Art of code. Apple implements @autoreleasepool {} by declaring a local variable __autoreleasepool of type __atautoreleasepool. When declaring a __autoreleasepool variable, the constructor __atautoreleasepool () is called, that is, execution atautoreleasepoolobj = Objc_autoreleasepoolpush (); When the current scope is out, the destructor ~__atautoreleasepool () is called, which is the execution of Objc_autoreleasepoolpop (atautoreleasepoolobj). This means that the implementation code for @autoreleasepool {} can be further simplified as follows:
/ * @autoreleasepool * / {
void * atautoreleasepoolobj = objc_autoreleasePoolPush ();
// user code, all objects that receive autorelease messages will be added to this autoreleasepool
objc_autoreleasePoolPop (atautoreleasepoolobj);
}
Therefore, the operation of a single autoreleasepool can simply be understood as a process of Objc_autoreleasepoolpush (), [object Autorelease], and objc_autoreleasepoolpop (void *) three.
Push operation
The Objc_autoreleasepoolpush () function mentioned above is essentially the push function of the called Autoreleasepoolpage.
void *
objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
So let's take a look at the role and execution of the push function of autoreleasepoolpage. A push operation actually creates a new autoreleasepool, and the specific implementation of Autoreleasepoolpage is to insert a pool_sentinel into the next position in Autoreleasepoolpage, and Returns the memory address of the inserted pool_sentinel. This address is the pool token we mentioned earlier as the function's entry when performing a pop operation.
static inline void *push()
{
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
The push function performs a specific insert operation by calling the Autoreleasefast function.
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);
}
}
The Autoreleasefast function performs a specific insert operation in which three different cases are processed separately:
-
When the current page exists and is not full, the object is added directly to the current page, which is where next points;
-
When the current page is present and full, a new page is created and the object is added to the newly created page;
-
When the current page does not exist, that is, when there is no page, the first page is created and the object is added to the newly created page.
Each push operation is invoked to create a new autoreleasepool that inserts a pool_sentinel into Autoreleasepoolpage and returns the memory address of the inserted pool_sentinel.
Autorelease operation
By nsobject.mm The source file, we can find the implementation of the-autorelease method:
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
By looking at the method call ((ID) self)->rootautorelease (), we find that the final call is the Autoreleasepoolpage autorelease function.
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
The implementation of the Autorelease function of Autoreleasepoolpage is quite similar to the implementation of the push operation for us to understand the capacity. Only the push operation inserts a Pool_sentinel, and the Autorelease action inserts a specific Autoreleased object.
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || *dest == obj);
return obj;
}
Pop operation
Similarly, the previously mentioned objc_autoreleasepoolpop (void *) function is essentially the pop function of the called Autoreleasepoolpage.
void
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
// fixme rdar://9167170
if (!ctxt) return;
Au
The entry parameter of the POP function is the return value of the push function, which is the memory address of the Pool_sentinel, which is the POOL token. When a pop operation is performed, all autoreleased objects after the memory address in the pool token are release. Until next on the page of the pool token is pointing to the pool token.
The following is a memory structure diagram of the autoreleasepool stack for a thread, with a total of two pool_sentinel in the Autoreleasepool stack, which is two autoreleasepool. The stack consists of three autoreleasepoolpage nodes, the first autoreleasepoolpage node is coldpage (), and the last Autoreleasepoolpage node is hotpage (). The first two nodes are full, and the last node holds the memory address of the newly added autoreleased object objr3.
At this point, if you perform a pop (TOKEN1) operation, the memory structure of the Autoreleasepool stack will become as shown:
Nsthread, Nsrunloop and NSAutoreleasePool
According to the description of Nsrunloop in Apple's official documentation, we can know that each thread, including the main thread, will have a dedicated Nsrunloop object and will be created automatically when necessary.
Each Nsthread object, including the application ' s main thread, have an Nsrunloop object automatically created for it as Nee Ded.
Similarly, according to the description of NSAutoreleasePool in Apple's official documentation, we know that the Nsrunloop object in the main thread (which should be the same in other threads at the system level, such as through Dispatch_get_global_queue ( Dispatch_queue_priority_default, 0) The system automatically creates a autoreleasepool and drain at the end of the event loop before each event loop gets to the thread. The Autoreleased object created in scenario 1 mentioned above is added to this automatically created Autoreleasepool by the system and released when the Autoreleasepool is drain.
The application Kit creates a 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.
In addition, NSAutoreleasePool mentions that each thread will maintain its own autoreleasepool stack. In other words Autoreleasepool are closely related to threads, and each autoreleasepool only corresponds to one thread.
Each thread (including the main thread) is maintains its own stack of NSAutoreleasePool objects.
Figuring out the relationship between Nsthread, Nsrunloop and NSAutoreleasePool can help us understand Objective-c's memory management mechanism as a whole, and understand what the system does for us, understanding the whole operation mechanism.
Summarize
See here, I believe you should have a further understanding of the OBJECTIVE-C memory management mechanism. Normally, we do not need to manually add Autoreleasepool, and use the thread to automatically maintain the autoreleasepool just fine. Based on the description of the Using autorelease Pool Blocks in Apple's official documentation, we know that we need to add autoreleasepool manually in the following three scenarios:
-
If you are writing a program that is not based on a UI framework, such as command-line tools;
-
If you write a loop that creates a lot of temporary objects;
-
If you created a worker thread.
Finally, I hope this article can help you, has fun!
The realization principle of objective-c autorelease Pool