1. Automatic release pool
1. Related concepts
-
It is generally OK to increment the reference count of an object at the beginning of a function or method and to decrease the reference count of an object when the function or method does not need it.
-
Problem: Some functions or methods need to return an object, and the system may destroy the object before it can be returned. To ensure that the object returned by a function or method is not destroyed before it is returned, NSAutoreleasePool is used.
-
Automatic release pool means that it is a container (collection) of objects, and automatic release pool guarantees delayed destruction of all objects in the pool. For auto-release pool purposes, all objects should be added to the auto-release pool so that the auto-release pool destroys all objects in the pool before destroying them.
-
Autorelease method. This method does not change the object’s reference count, it just adds the object to the automatic release pool. This method returns the object on which it was called.
-
When a program calls the autorelease method of an object on the autorelease pool described below, the method simply adds the object to the autorelease pool, which causes all objects in the pool to execute the release method when the autorelease pool is released.
-
The autoloquy pool destruction is the same as that of any normal object. The autoloquy pool object is destroyed automatically as long as its reference count is 0. All objects in the pool are reclaimed when NSAotoreleasePool’s dealloc method is called.
-
NSAutoreleasePool also provides a drain method to destroy objects in the autorelease pool. Unlike Release, release causes the reference count of the auto-release pool itself to be 0, allowing the system to reclaim the NSAutoreleasePool object, and all objects in the pool are reclaimed before the NSAutoreleasePool object is reclaimed. The drain method simply reclaims all objects in the pool and does not destroy the auto-release pool.
2. Run the logic
AutoReleasePool is an automatic memory reclamation mechanism for OC, which delays the release of variables added to AutoReleasePool. Under normal circumstances, created variables are released when they are out of scope, but if you add variables to AutoreleasePool, release is delayed and will not be released immediately if they are out of scope. Runloop is not released until it goes to sleep or is out of AutoReleasePool scope.
Auto release pool works by:
- The main thread corresponds to the program starting until loading is complete
Runloop
In adormancy
Status until the user clicks interactive wake upRunloop
- Each user interaction starts once
Runloop
Used to handle user click, interaction events Runloop
When awakened, it willAutoReleasePool is automatically created
And adds all deferred release objects toAutoReleasePool
- In a complete
Runloop
Before the execution is complete, the system automatically sends a message to theAutoReleasePool
Object sending inNews release
And thenDestruction of AutoReleasePool
The operation mechanism and relationship between AutoreleasePool and Runloop will be explained later in Runloop.
3. Effect analysis
Here is an example to illustrate the role of automatic release pools. We create strings in one of two ways:
NSString * string1 = [[NSString alloc] initWithFormat:@" Hello world..."] ; NSString * string2 = [NSString stringWithFormat:@" Hello world Auto relase..."] ;Copy the code
So what’s the difference between these two approaches? We understand its internal implementation through assembly:
-
Method 1
NSString * string1 = [[NSString alloc] initWithFormat:@"hello world..."] ;Copy the code
use
alloc
Out of the way the string is calledrelease
(assuming the string is not referenced by anything else, the variable will go out of scoperelease
). -
Way 2
NSString * string2 = [NSString stringWithFormat:@"hello world auto relase..."] ;Copy the code
use
stringWith
The way the string is inapi
The inside is going to be set toautorelease
Instead of manually releasing, the system will recycle, so it will be in the nearest automatic release pooldrain
orrelease
When it is recycled.
Here is a case study to understand the role of automatic release pools. In the case, a string is created in two ways and assigned to an __weak decorated member variable.
-
Scenario 1
__weak NSString *weakSrting; __weak NSString *weakSrtingAutoRelease; @implementation ViewController - (void)createStringFunc {NSString * string1 = [[NSString alloc] initWithFormat:@"hello world..."] ; weakSrting = string1; NSString * string2 = [NSString stringWithFormat:@" Hello world Auto relase..."] ; weakSrtingAutoRelease = string2; } - (void)viewDidLoad { [super viewDidLoad]; [self createStringFunc]; NSLog(@"weakSrting: %@", weakSrting); NSLog(@"weakSrtingAutoRelease: %@", weakSrtingAutoRelease); } - (void)viewWillAppear:(BOOL)animated { NSLog(@"view will appear weakSrting: %@", weakSrting); NSLog(@"view will appear weakSrtingAutoRelease: %@", weakSrtingAutoRelease); } - (void) viewDidAppear:(BOOL)animated { NSLog(@"view did appear weakSrting: %@", weakSrting); NSLog(@"view did appear weakSrtingAutoRelease: %@", weakSrtingAutoRelease); }Copy the code
View the run result:
WeakSrting, a string created using method 1, is released when createStringFunc is executed (end of scope). Weakreference weakSrting is also released. So weakSrting printing results are empty.
WeakSrtingAutoRelease is an object created in Mode 2. This object is automatically added to the current AutoReleasepool to delay the release. This object is an Autoreleased object. The autoreleased object was added to the most recent Autoreleasepool, and only when the autoreleasepool itself drains, Autoreleased objects in autoReleasepool are released.
WeakSrtingAutoRelease, which prints the object when it is printed in viewDidAppear, indicating that the object has not been released yet. The object must have been released sometime between the viewWillAppear and viewDidAppear methods, and it was released when the AutoReleasepool it was in was released. WeakSrtingAutoRelease We can set watchpoint (watchpoint set V weakSrtingAutoRelease) in LLDB debugging to see the object release process:
As you can see in the run stack, the weakSrtingAutoRelease object completes the release when the automatic release pool is released.
-
Scenario 2 (ARC)
This scheme is more intuitive. An @AutoReleasepool is manually added in the code. WeakSrtingAutoRelease will not be released in the automatic releasepool, but will be released when it is out of the automatic releasepool. See below:
However, the object weakSrtingAutoRelease created in mode 2 can be used normally in the automatic release pool, and it will be released out of the automatic release pool, playing the effect of delayed release.
But the use of
Method 1
String createdweakSrting
Why was it released in the automatic release pool? He’s not gonna join the auto-release pool? This problem will be explained below!!
2. Principle analysis of automatic release pool
1. Preliminary study on principle
The implementation principle of automatic release pool is shown in clang below:
When compiled, @Autoreleasepool becomes the following code:
__AtAutoreleasePool __autoreleasepool;
Copy the code
Global search for the __AtAutoreleasePool definition to find the __AtAutoreleasePool structure definition:
struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; };Copy the code
This structure provides a constructor, objc_autoreleasePoolPush, and a destructor, objc_autoreleasePoolPop. So the autoreleasepoolpool is essentially a structure that creates the autoreleasepoolPool through objc_autoreleasePoolPush, which releases the autoreleasepoolPOP.
By setting a symbolic breakpoint and viewing the assembly, you can determine the source code for the auto-release pool implementation in the libobjC.a. dylib library, which is most familiar to us.
2. Structural analysis
The following analysis through the source code. Trace the objc_autoreleasePoolPush method implementation as shown below:
It calls the objc_autoreleasePoolPush() method to continue tracing the code:
In the implementation of this method, it calls the Push method of AutoreleasePoolPage. So what is the structure of AutoreleasePoolPage? See below:
The AutoreleasePoolPage class comments provide the following key information:
- A thread’s auto-release pool is a bunch of Pointers
- Each pointer is either an object to be released or
POOL_BOUNDARY
(Auto release pool boundary – Sentinel object) - The stack is split into one
A list of bidirectional linked pages
Objects are added to the page and removed as needed - Thread local storage points to newly automatically freed hot page objects that are stored
What to make of the above comment information? AutoreleasePoolPageData implementation
class AutoreleasePoolPage; struct AutoreleasePoolPageData { #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS struct AutoreleasePoolEntry { uintptr_t ptr: 48. uintptr_t count: 16; static const uintptr_t maxCount = 65535; // 2^ 16-1}; static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!" ); #endif 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
Attribute description:
magic
Used to checkAutoreleasePoolPage
Whether the structure is complete;next
Points to the latest additionautoreleased
Object at the next location to which it points when initializedbegin()
thread
Point to current threadparent
That points to the parent, the first nodeparent
A value ofnil
child
That points to the child, the last nodechild
A value ofnil
depth
Is for depth, from0
Start and increase1
hiwat
On behalf ofhigh water mark
Maximum number of pushes flag
3. Source code implementation
Tracking push implementation source 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
In non-debug mode, the autoreleaseFast method is first called and the boundary object (sentinel object) is passed in. AutoreleaseFast implementation source 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 {
return autoreleaseNoPage(obj);
}
}
Copy the code
In this method, the current hotPage is first retrieved, and if it is not empty and not full, obj is added to the page; If the page is full, the autoreleaseFullPage method is called; If the current hotPage does not exist, that is, there is no page, the autoreleaseNoPage method is called. AutoreleaseNoPage implementation source code as follows:
After AutoreleasePoolPage is created, add the sentinel object first, and then add the obj. First look at the AutoreleasePoolPage constructor, as shown below:
Initialization is done by calling the constructor of AutoreleasePoolPageData and determining the linked list relationships between the pages. The AutoreleasePoolPageData attribute is 56 bytes. See below:
Because the next field in the page is used to set where the OBj is stored, and because each page has some properties that take up some space, the starting value of next is the 56 bytes shift at the beginning of the page, as determined by the begin() method in the constructor.
If the page is full, the autoreleaseFullPage method above is called, as shown in the implementation source code below:
AutoreleasePoolPage = AutoreleasePoolPage = AutoreleasePoolPage
Autoreleasepool
Is composed of multipleAutoreleasePoolPage
In the form of a bidirectional linked listAutoreleasepool
The basic principle of the automatic release pool: when created, will be in the currentAutoreleasePoolPage
Sets a marker bit (boundary) during which an object is calledautorelease
Object is added toAutoreleasePoolPage
In the- If the current page is full, a new page is initialized, which is then linked in a bidirectional list, and the initialized page is set to
hotPage
When automatically released from the poolpop
When, from the bottom uppop
, calling each object’srelease
Method until the flag bit is encountered
4. Full page threshold
How many objects can an autofree pool store on a page? It would be much easier to understand the auto-release pool if we could print out the auto-release pool data. The source code also provides methods for printing data structures:
void
_objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
__attribute__((noinline, cold))
static void printAll()
{
_objc_inform("##############");
_objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_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();
}
}
_objc_inform("##############");
}
Copy the code
Take a look at its internal storage structure with the following example:
As can be seen from the output above, the start page of the auto release pool is 0x10380a000, the address shift is 56 bytes, and then the sentry object is placed, and the address of the sentry object is 0x10380A038, followed by four objects. So how many can fit on one page? There is also a definition in the source code:
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
#define PAGE_MIN_SHIFT 12
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
Copy the code
Through the interpretation of the source code can be determined, its size is 1<<12, that is, 4096, and each page of its own attributes occupy 56 bytes, while the first page needs a sentinel object 8 bytes, so the home page can put (4096-56-8) / 8 = 504 objects. Verify:
As you can see from the output auto-release pool data structure, when 505 objects are put in, a new page is opened and there is only one object in the second page. (Sentinel objects will only be placed on the first page) so the first page can hold up to 504 objects, and then 505 objects per page.
3. Automatically release the pool
1. Object release instead of destruction
Take a look at the following example:
When the auto-release pool ends, instead of destroying the object, simply send a release message to the object stored in the auto-release pool.
2. Automatically release the nesting of the pool
Automatic release pools can be nested!
As you can see from this example, the automatic release of pool nesting does not affect the data structure, but just inserts one more sentinel object.
3. Which objects can be added to the automatic release pool
Still through the case analysis.
-
MRC environment
-
The ARC environment
Autorelase (alloc, init,copy, etc.); autorelase (alloc, init,copy, etc.);
2. StringWithFormt, which by its name is not held by the caller, is either automatically added to the auto-release pool or is a constant string that is not managed by reference counting.