preface
The previous article introduced the source code for reference counting and weak references. This article explores the source code for autorelease pooling and autorelease
autorelease
Autorelease is used to delay the release of objects.
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
Copy the code
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return(id)this; // If the type is tagged pointer, the current object is returnedif (prepareOptimizedReturn(ReturnAtPlus1)) return(id)this; // If prepareOptimizedReturn is True, return the current object directlyreturnrootAutorelease2(); RootAutorelease2}Copy the code
Let’s break down the function
- prepareOptimizedReturn
PrepareOptimizedReturn is used to optimize the return and is implemented as follows
static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) { assert(getReturnDisposition() == ReturnAtPlus0); // Ensure that the current acquired Disposition isfalse
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {// If optimizedreturn is currently allowedif (disposition) setReturnDisposition(disposition); // Set ReturnDisposition totrue
return true;
}
return false;
}
Copy the code
- rootAutorelease2
RootAutorelease2 is implemented simply by calling the AutoRelease method of AutoreleasePoolPage to drop the object into the automatic release pool
__attribute__((noinline,used))
id
objc_object::rootAutorelease2() { assert(! isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}
Copy the code
Automatic releasepool
First, take a look at the Autoreleasepool data structure
Then the above AutoreleasePoolPage: : autorelease (this) (id) the entrance, to analyze the source code from here
static inline id autorelease(id obj) { assert(obj); // Assert that the object is not empty (! obj->isTaggedPointer()); // Assert that the object is not TaggedPointer ID *dest __unused = autoreleaseFast(obj); assert(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); // Make sure dest does not exist or equal to EMPTY_POOL_PLACEHOLDER or objreturn obj;
}
Copy the code
- autoreleaseFast
Then go down to the source code for autoreleaseFast
static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); // Get the current top pageif(page && ! Page ->full()) {// If the page exists and is not fullreturnpage->add(obj); // Add the object to the current page}else if(page) {// If the page exists but is already fullreturnautoreleaseFullPage(obj, page); // Call autoreleaseFullPage}else{// If page does not existreturnautoreleaseNoPage(obj); // Call autoreleaseNoPage}Copy the code
You can see that the AutoreleasePoolPage is composed of one or more AutoreleasepoolPages
- hotPage
The hotPage method is used to get the current topmost page
static inline AutoreleasePoolPage *hotPage() { AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); // Use TLS to query available AutoreleasePoolPage objectsif ((id *)result == EMPTY_POOL_PLACEHOLDER) returnnil; // If the query result is EMPTY_POOL_PLACEHOLDER, return nilif(result) result->fastcheck(); // If result is not null, the fastCheck method is called, which does the work of thread detectionreturn result;
}
Copy the code
Key is defined as follows:
// Thread keys reserved by libc for our use.
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
Copy the code
- full
The full method is used to check if the current AutoreleasePoolPage is full
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool full() {
return next == end();
}
Copy the code
SIZE is defined as follows
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define PAGE_SIZE I386_PGBYTES
#define PAGE_MAX_SIZE PAGE_SIZE
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
Copy the code
- add
The Add function adds an object to an AutoreleasePoolPage
id *add(id obj) { assert(! full()); Unprotect (); // Set read/write status to id *ret = next; // faster than `returnnext-1` because of aliasing *next++ = obj; protect(); // Set it to read-onlyreturn ret;
}
Copy the code
- autoreleaseFullPage
The job of the autoreleaseFullPage function is to create an AutoreleasePoolPage and put the object in it
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; // Assign page->child if page->child existselsepage = new AutoreleasePoolPage(page); // If not, create a new AutoreleasePoolPage}while (page->full());
setHotPage(page); // Set the new AutoreleasePoolPage to hotPagereturnpage->add(obj); // Add the object to the new AutoreleasePoolPage}Copy the code
Constructor of AutoreleasePoolPage
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0)
{
if(parent) { parent->check(); assert(! parent->child); parent->unprotect(); parent->child = this; parent->protect(); } protect(); }Copy the code
- setHotPage
Set hotPage over TLS
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
Copy the code
- autoreleaseNoPage
The autoreleaseNoPage method is called if you did not create a Pool but generated an Autorelease object. In this method, you automatically create a hotPage for you and call Page -> Add (obj) to add the object to the stack of AutoreleasePoolPage
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 poolin 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",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) { // We are pushing a pool with no poolin place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder(); // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);setHotPage(page);
if(pushExtraBoundary) { page->add(POOL_BOUNDARY); // Add a nil delimit for each AutoreleasePoolPage // Push the requested object or pool.return page->add(obj);
}
Copy the code
- haveEmptyPoolPlaceholder
static inline bool haveEmptyPoolPlaceholder()
{
id *tls = (id *)tls_get_direct(key);
return (tls == EMPTY_POOL_PLACEHOLDER);
}
Copy the code
- setEmptyPoolPlaceholder
static inline id* setEmptyPoolPlaceholder()
{
assert(tls_get_direct(key) == nil);
tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
return EMPTY_POOL_PLACEHOLDER;
}
Copy the code
Once you have seen the process of adding AutoreleasePoolPage, take a look at the process of releasing AutoreleasePoolPage. AutoreleasePool is essentially a bidirectional linked list. The process of releasing AutoreleasePool is to delete the list from the last linked list and then delete it from the next linked list, as shown below:
AutoreleasePoolPage:: POP
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
Copy the code
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; EMPTY_POOL_PLACEHOLDER: a placeholder for when the autofree pool is emptyif(token == (void*)EMPTY_POOL_PLACEHOLDER) {// If the value is EMPTY_POOL_PLACEHOLDER // Popping the value from the top-level placeholder pool.if(hotPage()) {// Pool was used. Pop its contents normally. // Pool pages remain remain of tokensforre-use as usual. pop(coldPage()->begin()); // Destroy the entire autorelease pool}else {
// Pool was never used. Clear the placeholder.
setHotPage(nil); Set hotPage to nil}return; } page = pageForPointer(token); Stop = (id *)token; stop = (id *)token; // Token is converted to stopif(*stop ! = POOL_BOUNDARY) {// If stop is not stored as a boundaryif(stop == page->begin() && ! page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold pageinAn object is autoreleased with no pool // There are two cases where the first object stored on the first node of the automatic release pool is not a boundary character: // 1. The top-level pool is not freed, but the first node is left.else {
// Error. For bincompat purposes this is not
// fatal inExecutables built with old SDKs. // Not the first node of the auto-release pool, stop stores cases that are not boundary charactersreturnbadPop(token); // Call badPop()}}if (PrintPoolHiwat) printHiwat(); // If you want to print hiwat, print page->releaseUntil(stop); // Memory: delete empty childrenif (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill(a);setHotPage(parent);
} else if(DebugMissingPools && page->empty() && ! page->parent) { // specialcase: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill(a);setHotPage(nil);
}
else if(Page ->child) {// If page has child node // hysteresis: keep one empty childif page is more than half full
if(Page ->lessThanHalfFull()) {// If the page has occupied less than half of the page->child->kill(a); //killDrop page child}else if(page - > child - > child) {/ / if the page take up the space is already more than half, child node has child nodes and page page - > child child -- - > >kill(a); //killDrop child node}}}Copy the code
- pageForPointer
The addresses assigned to the AutoreleasePoolPage object are all 4096 aligned, that is, the addresses of the AutoreleasePoolPage object are all multiples of 4096. Therefore, when the token is converted to decimal, the value of 4096 is mod. You can obtain the offset of the token address to the AutoreleasePoolPage object address. Since the size of the AutoreleasePoolPage object itself is 56, if the token mod to 4096 is less than 56, an exception will be thrown. Otherwise, the offset subtracted from the token address is the address of the AutoreleasePoolPage object, which is converted to an AutoreleasePoolPage * pointer, which is the Page node where the token is located.
static AutoreleasePoolPage *pageForPointer(uintptr_t p) { AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; 4096 assert(offset >= sizeof(AutoreleasePoolPage)); Result = (AutoreleasePoolPage *)(p-offset); // If the remainder is smaller than the size of AutoreleasePoolPage, an exception is raised. // The decimal number p subtracts the remainder offset, resulting in an AutoreleasePoolPage * pointer result-> fastCheck (); // Check according to the configurationreturn result;
}
Copy the code
- badPop
This function should not be called normally, only when using older SDKS. When badPop occurs, an error log is logged and the automatic release pool is destroyed.
static void badPop(void *token)
{
// Error. For bincompat purposes this is not
// fatal inExecutables built with old SDKs. // This bug is not fatal to older SDKsif(DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0, 2_0)) {// OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal. Error calling badPop _objc_fatal ()"Invalid or prematurely-freed autorelease pool %p.", token); } // Bad Pop is warned once. // Under the Old SDK, Bad Pop will record a journal static bool rolling stock =false;
if(! complained) { complained =true; // Output a series of messages to crashlogCrash _objC_inform_now_and_on_crash ("Invalid or prematurely-freed autorelease pool %p. "
"Set a breakpoint on objc_autoreleasePoolInvalid to debug. "
"Proceeding anyway because the app is old "
"(SDK version " SDK_FORMAT "). Memory errors are likely.", token, FORMAT_SDK(sdkVersion())); } objc_autoreleasePoolInvalid(token); // Destroy the automatic release pool containing tokens}Copy the code
- releaseUntil
The auto-release pool destroys the most important link in the object. The caller is found by using pageForPointer(), the page node where the token is located, and the parameter is token. Start with hotPage, use the next pointer to traverse the list of autoRelease objects stored in the node, release each object once, and clear the pointer to Next. If the hotPage is empty, the parent is looped forward and the next pointer is iterated over the parent until the next pointer points to token. Because the token is in this, the hotPage should be this.
Void releaseUntil(id *stop) {// Recursion is not used here to prevent stack overflowwhile(this->next ! // start hotPage from hotPage()in case-release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); / / remove hotPagewhile(page->empty()) {// Start with the node page and find the first non-empty node page = page->parent; // If the page is not empty, look at the parent node of the pagesetHotPage(page); // Set the new page node to HotPage} page->unprotect(); Id obj = *--page->next; Memset ((void*)page->next, SCRIBBLE, sizeof(*page->next)); // Clear the memory pointed to by next to SCRIBBLE page->protect(); // Set memory lock if necessaryif(obj ! Objc_release (obj); // Release the object}}setHotPage(this); // Set this object to hotPage#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
Copy the code
- kill
All the objects that need to be released in the automatic release pool have been completed. At this time, all the page nodes after hotPage have been emptied, and the memory of these nodes needs to be reclaimed. The operation scheme is to recycle the memory from the last node to the caller node.
void killAutoreleasePoolPage *page = this; AutoreleasePoolPage *page = this; // Start with the callerwhile(page->child) page = page->child; AutoreleasePoolPage *deathptr;do{// Start traversing from the last node to the calling node deathptr = page; // keep the current traversal to the node page = page->parent; // walk forwardif(page) {// If there is a value page->unprotect(); // If necessary, unlock memory lock page->child = nil; // child null page->protect(); // Set memory lock if necessary} delete deathptr; // Reclaim the node that was just saved, override delete, call free internally}while(deathptr ! = this); }Copy the code
Refer to the article
Autorelease, autoreleasepool, autorelease, autoreleasepool