AutoreleasePool
- NSAutoreleasePool:
An object that supports Cocoa’s reference-counted memory management system.
- Example:
- In the MRC environment
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Code benefitting from a local autorelease pool. [pool release]; Copy the code
- Under the environment of the ARC
@autoreleasepool { // Code benefitting from a local autorelease pool. } Copy the code
- It can also be used in MRC environment
@autoreleasepool
The block
See the underlying implementation through Clang
- Through the command
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m
generatemain.cpp
file - To view
autoreleasepool
Corresponding code
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
Copy the code
- Continue to search
__AtAutoreleasePool
See,__AtAutoreleasePool
It’s a structure
struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; };Copy the code
__AtAutoreleasePool __autoreleasepool;
It’s almost executed__AtAutoreleasePool
Constructor and destructor of
atautoreleasepoolobj = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(atautoreleasepoolobj);
Copy the code
Observe the execution flow through assembly
- Directly in the
main
The function hits a breakpoint and goes to the debug interface
- Sign breakpoint
objc_autoreleasePoolPush
Run again to enter the breakpoint
- You can see
objc_autoreleasePoolPush
Is in thelibobjc.dylib
Can passobjc
Source view implementation
The underlying implementation of AutoreleasePool
objc_autoreleasePoolPush
objc_autoreleasePoolPush
Implementation code of
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
Copy the code
AutoreleasePoolPage
Is an inheritance fromAutoreleasePoolPageData
The class of
/*********************************************************************** Autorelease pool implementation A thread's autorelease pool is a stack of pointers. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. Thread-local storage points to the hot page, where newly autoreleased objects are stored. Auto-release pool The auto-release pool that implements threads is the stack of Pointers. Each pointer is either the object to be released or POOL_BOUNDARY automatically releases the pool boundary. The pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, each object hotter than the sentinel is released. The stack is divided into two bidirectional linked lists of pages. Pages are added and removed as needed. Thread local storage points to newly automatically published hot page objects that are stored. **********************************************************************/ class AutoreleasePoolPage : private AutoreleasePoolPageData { friend struct thread_data_t; public: 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 private: ...... }Copy the code
- You can also see from the above page that the maximum size of a page is 4096.
AutoreleasePoolPageData
The structure of the
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
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
- Go back to
AutoreleasePoolPage::push()
thepush
function
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
- Go down to
autoreleaseFast
function
static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && ! Page ->full()) {return page->add(obj); Return autoreleaseFullPage(obj, page); return autoreleaseFullPage(obj, page); return autoreleaseFullPage(obj, page); Return autoreleaseNoPage(obj); return autoreleaseNoPage(obj); }}Copy the code
add
The implementation of the
id *add(id obj) { ASSERT(! full()); unprotect(); id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; protect(); return ret; }Copy the code
autoreleaseFullPage
The implementation of the
static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. ASSERT(page == hotPage()); ASSERT(page->full() || DebugPoolAllocation); do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); // Create a new page setHotPage(page); return page->add(obj); }Copy the code
autoreleaseNoPage
This method is called when there is no page.
static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet ASSERT(! hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { // We are pushing a second pool over the empty placeholder pool // or pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary = true; } else if (obj ! = POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY && ! DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder'd pool. // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); // Push a boundary on behalf of the previously allocated pool. If (pushExtraBoundary) page->add(POOL_BOUNDARY); } // Push the requested object or pool. return page->add(obj); }Copy the code
new AutoreleasePoolPage
The initialization method of,begin()
The implementation of the
AutoreleasePoolPage(AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(), objc_thread_self(), newParent, newParent ? 1+newParent->depth : 0, newParent ? newParent->hiwat : 0) { if (parent) { parent->check(); ASSERT(! parent->child); parent->unprotect(); parent->child = this; parent->protect(); } protect(); } id * begin() { return (id *) ((uint8_t *)this+sizeof(*this)); }Copy the code
- Automatic release of the pool during initialization
- _next refers to the next location of the newly added Autoreleased object, initialized to begin().
- Thread refers to the current thread.
- Parent refers to the parent node, and the first node is nil.
- Child, the last node is nil.
- Depth Node depth. The default value is 0.
- Hiwat represents the maximum number of stacks.
objc_autoreleasePoolPop
objc_autoreleasePoolPop
Out of the stack,pop
The implementation of the
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. page = hotPage(); if (! page) { // Pool was never used. Clear the placeholder. return setHotPage(nil); } // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. page = coldPage(); token = page->begin(); } else { 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 page in place // 2. an object is autoreleased with no pool } else { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. return badPop(token); } } if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) { return popPageDebug(token, page, stop); } return popPage<false>(token, page, stop); }Copy the code
popPage
The implementation of the
static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); page->releaseUntil(stop); // memory: delete empty children if (allowDebug && DebugPoolAllocation && page->empty()) { // special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; page->kill(); setHotPage(parent); } else if (allowDebug && DebugMissingPools && page->empty() && ! page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); }}}Copy the code
pop
The implementation andpush
After releasing the last page, delete that, too, and iterate through the upper page, releasing the objects in it until you encounter the sentry. The entire release pool is released
_objc_autoreleasePoolPrint
- And we’ve done that by putting
_objc_autoreleasePoolPrint
Declared in the main function to print the auto-free pool data
#import <Foundation/Foundation.h>
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0; i < 5 ; i++) {
NSObject *obj = [[NSObject alloc] autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
Copy the code
- The offset address of the sentinel is 0x38, which is exactly the size of the structure 56. We know from the above that the maximum size of each page is 4096, so each page can store 505 automatically released objects. There is one sentinel on the first page, so 504 objects can be stored
- View by modifying the code
- Automatically release the nesting of the pool
- You can see that the nested auto-release pool does not create a new page, but instead adds a sentinel.