What is AutoreleasePool
AutoreleasePool is a memory management mechanism used in iOS development. Objects called autoRelease are placed in the cache pool to be released later. When the cache pool needs to be cleared, A release message is sent to these Autoreleased objects.
Create a new Xcode project and change it to MRC:
MRC
retain/release/autorelease
int main(int argc, const char * argv[]) {
NSLog(@"-A-");
Coder *coder = [[Coder alloc] init];
[coder release];
NSLog(@"-B-");
return 0;
}
// log
-A-
Coder dealloc
-B-
Copy the code
You create the coder object with alloc, let its reference count increase, and then call the Release method to complete the release. If you use autorelease, you need to use the automatic cache pool, which looks like this:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"-A-");
Coder *coder = [[[Coder alloc] init] autorelease];
NSLog(@"-B-");
}
NSLog(@"-C-");
return 0;
}
// log
-A-
-B-
Coder dealloc
-C-
Copy the code
The coder object here is released automatically when it is out of scope of the automatic cache pool.
Not all cases are automatically released out of scope, more on that later.
What did @autoreleasepool do
Convert main.m to C++ code using the xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m command.
You’ll see that @Autoreleasepool is converted to a member variable:
__AtAutoreleasePool __autoreleasepool;
Copy the code
An implementation of the __AtAutoreleasePool structure:
struct __AtAutoreleasePool {__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); }void * atautoreleasepoolobj;
};
Copy the code
__AtAutoreleasePool() is the constructor, called when the structure is created, and ~__AtAutoreleasePool() is the destructor, called when the structure is destroyed.
int main(int argc, const char * argv[]) {
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
// @autoreleasepool code inside the parentheses
objc_autoreleasePoolPop(atautoreleasepoolobj);
return 0;
}
Copy the code
This explains why not all cases are automatically released out of the @Autoreleasepool scope, as this is just a syntactic sugar that essentially calls the Push&PoP method above.
AutoreleasePoolPage
Runtime source address, objC4-723 used here
Check the source code for Push&Pop above:
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
Copy the code
This calls the AutoreleasePoolPage class’s Push&Pop function, which I’ll stop here. Let’s look at how AutoreleasePoolPage is structured, with only member variables:
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
// ...
}
Copy the code
The next pointer here points to the next location of the newly added AutoRelease object. This is not easy to understand, so I’ll just say AutoreleasePoolPage first.
The autorelease pool is actually a wrapped C++ class, AutoreleasePoolPage, in the form of a bidirectional linked list. Each AutoreleasePoolPage object allocates 4096 bytes of memory (the size of a page of virtual memory), except for the space occupied by instance variables above, and the remaining space is used to stack autoRelease objects. When the AutoreleasePoolPage space is used up, an AutoreleasePoolPage object is created as a linked list and the address of the AutoRelease object is stored in it. As shown in the figure:
Source code analysis
Back to the original C++ code:
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(atautoreleasepoolobj);
Copy the code
When we call Push, we get a return value, which is passed in as an argument to Pop. Here’s how it works. Push function Push function Push function Push
/ / simplified
static inline void *push(a)
{
id *dest;
dest = autoreleaseFast(POOL_BOUNDARY);
return dest;
}
Copy the code
There is a POOL_BOUNDARY that deserves our attention, but if we look at its definition we will see that it is actually a nil equivalent macro definition:
# define POOL_BOUNDARY nil
Copy the code
That is, POOL_BOUNDARY is just a sentinel value. Enter autoreleaseFast (…). Function:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if(page && ! page->full()) {/ / 1.
return page->add(obj);
} else if (page) {
/ / 2.
return autoreleaseFullPage(obj, page);
} else {
/ / 3.
returnautoreleaseNoPage(obj); }}Copy the code
HotPage refers to the currently used AutoreleasePoolPage node, and coldPage refers to the full list node.
AutoreleasePoolPage = AutoreleasePoolPage = AutoreleasePoolPage
- 1. The current
page
If it exists and is not full, the object is added directly to the currentpage
In the. - 2. The current
page
If it exists and is full, create a new onepage
And add the object to the newly createdpage
, and then link the two list nodes. - 3. The current
page
If it does not exist, create the firstpage
And add the object to the newly createdpage
In the.
Push
POOL_BOUNDARY
Pop
page
Pop
@autoreleasepool {
@autoreleasepool{}}Copy the code
It is also important to note that this is implemented using a double-linked list. New pages are created only after the current page space is used up. There is not an AutoreleasePoolPage object for each @Autoreleasepool.
Next look at the source of Pop:
/ / simplified
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token);
stop = (id *)token;
// 1. POOL_BOUNDARY releases the 'autoreleased' object based on the token
page->releaseUntil(stop);
// hysteresis: keep one empty child if page is more than half full
// 2. After releasing the 'Autoreleased' object, destroy the extra pages.
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if(page->child->child) { page->child->child->kill(); }}Copy the code
Nothing to say here, come to releaseUntil(…) Internal:
/ / simplified
void releaseUntil(id *stop)
{
/ / 1.
while (this->next ! = stop) { AutoreleasePoolPage *page = hotPage();/ / 2.
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
/ / 3.
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
/ / 4.
setHotPage(this);
}
Copy the code
- 1. The external loop iterates one by one
autoreleased
Object until traversed tostop
thisPOOL_BOUNDARY
。 - 2. If the current
hatPage
There is noPOOL_BOUNDARY
That will behatPage
Set to the parent node. - 3. To the current
autoreleased
Object to sendrelease
The message. - 4. Configure the configuration again
hatPage
.
Let’s look at the implementation of autoRelease, which goes directly to the page autoRelease:
/ / simplified
static inline id autorelease(id obj)
{
id *dest __unused = autoreleaseFast(obj);
return obj;
}
Copy the code
The same function called in the push operation above, autoreleaseFast, is nothing to say.
Here’s how automatic cache pooling works at the source level.
AutoreleasePool and runloop
For runloop knowledge, see my previous article iOS runloop
The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler, used to deal with automatic buffer pool.
Print the main thread runloop for confirmation.
print(RunLoop.main)
Copy the code
Observer
activities = 0x1
activities = 0xa0
runloop
runloop
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), / / 1
kCFRunLoopBeforeTimers = (1UL << 1), / / 2
kCFRunLoopBeforeSources = (1UL << 2), / / 4
kCFRunLoopBeforeWaiting = (1UL << 5), / / 32
kCFRunLoopAfterWaiting = (1UL << 6), / / 64
kCFRunLoopExit = (1UL << 7), / / 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code
0x1 (equal to 1) corresponds to kCFRunLoopEntry, which creates an automatic release pool by calling _objc_autoreleasePoolPush() in its callback when the first Observer is about to enter the Loop. Its order is -2147483647, the highest priority, ensuring that the creation of the cache pool occurs before all other callbacks.
0xa0 (hexadecimal equals 160 equals 32+128) corresponds to kCFRunLoopBeforeWaiting&kCFRunLoopExit, the second Observer monitors two events: Call _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() torelease the old pool and create a new one when you are ready to go to sleep. Call _objc_autoreleasePoolPop() torelease the automatic release pool when exiting the Loop. The order of this Observer is 2147483647, the lowest priority, ensuring that its release of the cache pool occurs after all other callbacks.
So for our application, autoreleased objects are more likely to be released while Runloop is asleep.
reference
AutoreleasePool also has some practical tips, such as solving memory problems with autoreleased objects in loops, and so on.
Autorelease behind the black screen
Objective-c Autorelease Pool