OC Basic principles of exploration document summary

Automatic release pooling is an automatic garbage collection mechanism in OC. It is a scheme of memory management.

Main Contents:

  1. Automatic release pool awareness
  2. The nature of auto-release pools
  3. Analysis of the process of pushing and unloading of automatic release pool

1. Understanding of automatic release pool

1.1 introduction

Automatic release pool is an automatic garbage collection mechanism for OC to manage the release of objects. It can delay the release timing of objects added to autoreleasePool.

Normally, when an object stops pointing to a variable, the object is immediately released. However, if the object is added to the automatic release pool, the object is not immediately released. Instead, runloop will not be released until it has gone to sleep or gone out of autoreleasePool scope {}.

Description:

  1. From the start of the program until the load is complete, the runloop corresponding to the main thread is dormant, waiting for user interaction to wake up the runloop.
  2. The Runloop wakes up once for every user interaction and handles all user clicks, touches, and so on.
  3. When runloop listens for interaction events, it creates an automatic release pool and adds any delayed release objects to the pool.
  4. Before a full runloop is completed, a Release message is sent to all objects in the auto-release pool and then the auto-release pool is destroyed

1.2 Viewing the essence of autoreleasePool

Analysis was performed by Clang

Definition code:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
}
Copy the code

View the compiled results

struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; }; int main(int argc, char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; }}Copy the code

Description:

  • You can see that the underlying implementation is an __AtAutoreleasePool structure
  • The scope of the auto-release pool is __AtAutoreleasePool() and the destructor of ~__AtAutoreleasePool(). The code in the middle is the code executed in the auto-release pool {}
  • The constructor and destructor are implemented using objc_autoreleasePoolPush and objc_autoreleasePoolPop, respectively.

Conclusion:

  • The auto-release pool is essentially a structure, a class (explained later)
  • Calling the constructor is the beginning of the scope
  • Calling the destructor is the end of the scope
  • {} is the scope, the advantage is clear structure, readable, can be created and destroyed in time

Customize a structure to validate the scope

Struct WYTest{WYTest(){printf(" scope start - %s\n", __func__); } ~WYTest(){printf(" end of scope - %s\n", __func__); }}; Int main(int argc, char * argv[]) {{printf("WY: hey hey - before scope - %s\n", __func__); WYTest test; Printf ("WY: haha - scope - %s\n", __func__); } printf("WY: hee hee - scoped - %s\n", __func__); }Copy the code

Running results:

WYTest WY: ha ha - scope - main end - ~WYTest WY: Hee hee - post - scope - mainCopy the code

Description:

  • You can see that the scope is the range between the constructor and destructor
  • Execute WYTest test in {}, so the {} range is from the statement to the end of the code block

2. Low-level analysis

2.1 How does the objC source code describe auto-release pooling

Autorelease pool implementation - A thread's autorelease pool is a stack of pointers. - Each pointer is either an object torelease, or POOL_BOUNDARY which is an autorelease pool boundary. Each pointer is the object to release, or POOL_BOUNDARY, which is the automatic release 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 pool token is a pointer to the POOL_BOUNDARY for that pool. After the pool is ejected, each 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. The stack is divided into two bidirectional linked lists of pages. Add and remove pages as needed. - Thread-local storage points to the hot page, where newly autoreleased objects are stored. Thread local storage points to the hot page, which stores the newly automatically freed objects.Copy the code

Description:

  • Automatic release pooling is thread dependent. Threads have automatic release pools (runloop-based management).
  • The auto-release pool is essentially a stack that stores Pointers, including object Pointers and boundary Pointers.
  • The auto-release pool has a token pointing to the pool boundary, and when the pool performs a POP, all non-boundary objects are released. All of these objects are hotter than the boundary objects. The boundary object is not going to be released, it’s going to be the boundary.
  • At the bottom of the stack is a two-way linked list structure, so objects can be added and removed quickly.
  • Thread local storage points to the hot page that stores the newly automatically freed object.
  • The stack structure of a bidirectional linked list constitutes the page

2.2 Understanding of automatic release pool

2.2.1 Viewing source code

/ / * * * * * * * * * * * push method * * * * * * * * * * * void * objc_autoreleasePoolPush (void) {return AutoreleasePoolPage: : push (); } / / * * * * * * * * * * * pop method * * * * * * * * * * * void objc_autoreleasePoolPop (void * CTXT) {AutoreleasePoolPage: : pop (CTXT); }Copy the code

Description:

  • The underlying structure of the automatic release pool is AutoreleasePoolPage, which is a page structure
  • The pool is managed by calling push and POP, push creates the pool, pop is the destructor pool, when pop releases all the objects in the pool

2.2.2 AutoreleasePoolPage

//************ macro definition ************ #define PAGE_MIN_SIZE PAGE_SIZE #define PAGE_SIZE I386_PGBYTES #define I386_PGBYTES 4096 / * bytes per page * 80386 / / / * * * * * * * * * * * * class definition * * * * * * * * * * * * class AutoreleasePoolPage: private AutoreleasePoolPageData { friend struct thread_data_t; Public: // page SIZE 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: ... // Constructor AutoreleasePoolPage(AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(),// Start storage location objc_thread_self(),// pass the current thread, the current thread through TLS to get newParent, newParent? 1+newParent->depth: 0,// If the first page is 0, then the depth of the previous page is +1 newParent? newParent->hiwat : 0) {... } // Destructor ~AutoreleasePoolPage() {... }... Id * begin() {... } // page end position id * end() {... } // whether the page is empty bool empty() {... } // page full bool full() {... Bool lessThanHalfFull() {... } // add(id obj){... } // releaseAll objects void releaseAll() {... } // Release all objects until stop void releaseUntil(id *stop) {... } // Kill void kill() {... } static void tls_dealloc(void *p) {... } // Get AutoreleasePoolPage static AutoreleasePoolPage *pageForPointer(const void *p) {... } static AutoreleasePoolPage *pageForPointer(uintptr_t p) {... } static inline bool haveEmptyPoolPlaceholder() {... } // Set the empty pool placeholder static inline ID * setEmptyPoolPlaceholder(){... Static inline AutoreleasePoolPage *hotPage(){static inline AutoreleasePoolPage *hotPage(){... } static inline void setHotPage(AutoreleasePoolPage *page) {... } static inline AutoreleasePoolPage *coldPage() {... } static inline id *autoreleaseFast(id obj){... } // Add auto-release object, Static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {static __attribute__((noinline)) ID *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {... Static __attribute__((noinline)) id *autoreleaseNoPage(id obj){... Static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {... } public: // Release static inline ID autorelease(id obj){... } static inline void *push() {... } // Compatible with the old SDK stack method __attribute__((noinline, cold)) static void badPop(void *token){... Template <bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop){... } __attribute__((noinline, cold)) static void popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){... } static inline void pop(void *token){... } static void init(){... } / / print __attribute__ ((noinline, cold)) void print () {... } // printAll __attribute__((noinline, cold)) static void printAll(){... } // printHiwat __attribute__((noinline, cold)) static void printHiwat(){... }Copy the code

Description:

  • AutoreleasePoolPage inherits from AutoreleasePoolPageData

  • AutoreleasePoolPage is 4096 bytes in size

  • Construct with AutoreleasePoolPage(), which is internally visible as constructed with AutoreleasePoolPageData()

  • And ~AutoreleasePoolPage()

  • You can look at the API as well, and we’ll use it later on in the pool

  • Page operations such as full(), hotPage(), autoreleaseNewPage(id obj), push(), pop(void *token)

  • There are operations on objects such as add(), releaseAll(), AutoRelease (id obj)

2.2.3 AutoreleasePoolPageData

It is the parent class of AutoreleasePoolPage

class AutoreleasePoolPage; Struct AutoreleasePoolPageData {magic_t const magic; __unsafe_unretained id *next; // The location of the newly added autoreleased object points to begin() __unsafe_unretained id *next; Pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; AutoreleasePoolPage *child; Uint32_t const depth; // uint32_t const depth; Uint32_t hiwat; __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

Description:

  • The auto-release pool has its own thread
  • An automatic release pool is made up of multiple pages in a bidirectional linked list structure that can point to the previous and next pages
  • Depth Indicates the depth of the current page. If the page is the first page, the depth is 0. If the page is the second page, the depth is 1
  • And the AutoreleasePoolPageData structure has a memory size of 56 bytes
    • The type of attribute magic is magic_T structure, which occupies m memory size [4]. Memory occupied (i.e. 4*4=16 bytes)
    • The attributes next, Thread, parent, and Child are all 8 bytes (i.e. 4*8=32 bytes)
    • Uint32_t attribute depth, hiwat type is uint32_t, actual type is unsigned int, both of 4 bytes

2.3 Automatic release pool creation, that is, AutoreleasePoolPage construction

2.3.1 push ()

Code:

static inline void *push() { id *dest; If (slowPath (DebugPoolAllocation)) {// Each autoRelease pool starts on a new pool page autoreleaseNewPage(POOL_BOUNDARY); } else {dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }Copy the code

Description:

  • Call push to construct the pool, and we can see that there are two ways to construct it
  • The first is slowPath and debug, so just look at the second autoreleaseFast method, which presses a boundary object while creating the pool

2.3.2 autoreleaseFast ()

Source:

Static Inline ID *autoreleaseFast(id obj) {AutoreleasePoolPage *page = hotPage(); // If the current page exists and is not filled, we can add a release object or boundary object if (page &&! page->full()) { return page->add(obj); Return autoreleaseFullPage(obj, page);} else if (page) {return child page (obj, page); Return autoreleaseNoPage(obj); return autoreleaseNoPage(obj); }}Copy the code

Description:

  • You can see that there are three ways to get the current page
  • If the current page exists and can be stored without filling up, simply add
  • If the current page exists but is already full, it needs to be stored to other pages via autoreleaseFullPage(), or a new page needs to be created if all pages are full
  • If there is no current page at all, the pool doesn’t exist yet, so create a new first page autoreleaseNoPage()

2.3.3 the add ()

Source:

Return (id *) ((uint8_t *)this+sizeof(*this)); Return (id *) ((uint8_t *)this+SIZE); Bool empty() {return next == begin(); return next == begin(); Return next == end(); return next == end(); return next == end(); } // assign the object obj to the current object position next, and add next++ id *add(id obj) {ASSERT(! full()); unprotect(); Id *ret = next; // Next ++ = next; // next++ = next; // faster than 'return next-1' because of aliasing protect(); return ret; }Copy the code

Description:

  • Are relatively simple, and notes also said very clear
  • One thing to note is that next takes eight bytes and stores the address of an object, which represents the location of the next object to be inserted
  • So we can just assign the inserted object to Next and the insertion is successful, and then we’ll bring next++ again to the location of the next object insertion

2.3.4 autoreleaseFullPage ()

Source:

// Get the next unfilled page, 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); If (page->child) page = page->child; if (page->child) page = page->child; // Create a new page if there is no child else Page = new AutoreleasePoolPage(page); } while (page->full()); // Set the current page to setHotPage(page); Return page->add(obj); }Copy the code

Description:

  • If the current page is already full, enter this method
  • There are two operations that loop through to determine if the next page is full, if not, set the page to the current page, and add objects
  • If it’s full, move on to the next page, and if the last page is still full, create a new page to add objects

2.3.5 autoreleaseNoPage ()

Source:

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 = new AutoreleasePoolPage(nil); // Set the first page to the current page setHotPage(page); // create a void void void void void void void void void void void void void void void If (pushExtraBoundary) {page->add(POOL_BOUNDARY); Return page->add(obj); // The requested object or pool should be released. }Copy the code

Description:

  • You can see that this method is used to create the first page, and the first page is set to the current page
  • If it’s the first page you need to add a boundary object and then push an object to release
  • The new page is created using the constructor AutoreleasePoolPage
  • When we look at the idea of automatic release pools, we know that they are implemented through the constructor of their parent class AutoreleasePoolPageData

2.3.5 summary

  • The auto-release pool consists of pages organized in a bidirectional linked list, and the data structure of the page is a stack, following the principle of first in, last out
  • Constructing an automatic release pool is essentially creating the first page, which is created using the constructor of AutoreleasePoolPage
  • The constructor adds members of the structure to the page and, if it is the first page, a boundary object
  • If there is only one page, the first page is the current page
  • When you add objects, you fill up the previous page and then add it to the next page. If all the pages are full, you continue to create the page
  • Page creation is done through the constructor of AutoreleasePoolPage, but except for the first page, there are no boundary objects on any of the pages
  • The later the page is created, the deeper it is in the pool

Calculation of size:

  • How big a page is:
    • As defined in the AutoreleasePoolPage structure, it is 4096 bytes
  • How big are the members of a one-page structure: Based on the members of the structure of the parent AutoreleasePoolPageData, the size of the members is 56 bytes
    • The type of attribute magic is magic_T structure, which occupies m memory size [4]. Memory occupied (i.e. 4*4=16 bytes)
    • The attributes next, Thread, parent, and Child are all 8 bytes (i.e. 4*8=32 bytes)
    • Attribute depth, hiwat type is uint32_t, actual type is unsigned int, both of 4 bytes (i.e. 2*4=8 bytes)
  • How big is the boundary object of a page
    • All objects are actually stored as Pointers, so it’s 8 bytes
  • How many Release objects are stored on a page
    • As you can see, each page has a total of 4096 bytes. Subtract 56 bytes from the structure members, leaving 4040 bytes. An object takes up 8 bytes, so 505 objects can be stored.
    • The first page has a boundary object, so 504 objects can be added to the first page, and 505 objects can be added to the other pages
  • Depath how to count:
    • Each page has a depth that represents the position of the page in the auto-release pool. The depth is 0 if the first page means no depth, and 1 if the second page

Autorelease underlying analysis

2.4 The process of adding an object to an autorelease pool, that is, the process of autorelease

2.4.1 Viewing the underlying source code of AutoRelease

Code:

__attribute__((aligned(16), flatten, noinline)) id objC_AutoRelease (id obj) {// If not an object, return if (! obj) return obj; If (obj->isTaggedPointer()) return obj; if (obj->isTaggedPointer()) return obj; return obj->autorelease(); }Copy the code

Description:

  • You can see that this is actually implemented by calling autoRelease ()

Autorelease 2.4.2 ()

The main way to add objects is through the autoreleaseFast function

👇 Inline ID objc_object:: autoRelease () {ASSERT(! isTaggedPointer()); If (fastPath (! ISA()->hasCustomRR())) { return rootAutorelease(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease)); } 👇 inline ID objc_Object ::rootAutorelease() {// If (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; return rootAutorelease2(); } 👇 __attribute__((noinline,used)) id objc_object::rootAutorelease2() {ASSERT(! isTaggedPointer()); return AutoreleasePoolPage::autorelease((id)this); } 👇 static inline ID autorelease(id obj) {ASSERT(obj); ASSERT(! obj->isTaggedPointer()); Id *dest __unused = autoreleaseFast(obj); ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; }Copy the code

Description:

  • Step by step, the result is the autoRelease function in the AutoreleasePoolPage structure
  • The final step is to add the object by calling autoreleaseFast, which we’ve already analyzed but won’t do here.

2.4 Automatic release Pool release process, that is, the process of POP

Against 2.4.1 pop ()

Source:

Static inline void pop(void *token) {AutoreleasePoolPage *page; id *stop; If (token == (void*)EMPTY_POOL_PLACEHOLDER) {// If (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Get current page = hotPage(); if (! // Pool was never used. Clear the placeholder. // If the current page does not exist, return setHotPage(nil); } // Pool pages remain remain of allocated tokens for re-use as usual. // If the current page exists, Then set the current page to coldPage and the token to the start of coldPage = coldPage(); token = page->begin(); } else {page = pageForPointer(token); } stop = (id *)token; // Check whether the last position is sentinel if (*stop! If (stop == page->begin() &&! Page ->parent) {// If it is the first position, and there is no parent, do nothing // Start of page may be 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 {// If it is the first place, and there is a parent node, 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

Description:

  • If the current page does not exist, there are no more pages in the pool and the pool is empty.
  • Delete the page through popPage

2.4.2 popPage ()

Source:

Template <bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); Release page->release until (stop); // memory: Delete empty children // If the page is empty, delete the page directly, If (allowDebug && DebugPoolAllocation && Page ->empty()) {// Special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; // Delete page->kill(); // Set previous page to current page 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

Description:

  • To delete a page, you need to delete the objects stored in the page first
  • If the page is empty, delete it and set the previous page to the current one
  • If the page is already the first page, the pool is empty and the pool is emptied
  • If the next page still exists, delete the next page first

2.4.3 releaseUntil ()

Source:

Void releaseUntil(id *stop) {// Not recursive: We don't want to blow out the stack: // If a thread accumulates a stupendous amount of garbage While (this->next! = stop) {// start hotPage() every time, in case -release // autorelease more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, // If the current page is empty while (page->empty()) {// Assign page to parent page = page->parent; // Set the current page to the parent page setHotPage(page); } page->unprotect(); Obj = *--page->next; Memset ((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj ! = POOL_BOUNDARY) {// Release objc_release(obj); }} // Set the current page setHotPage(this); #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

Description:

  • Through the loop, determine whether the object is equal to stop, its purpose is to release all objects before stop
  • The object is first released by getting next for the page (that is, the last object in the page), and decrement next to get the last object
  • Determine if it is a sentinel object, and if not, automatically call objc_release to release it

2.4.4 kill ()

Source:

// Delete page void kill() {// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage AutoreleasePoolPage *page = this; While (page->child) page = page->child; AutoreleasePoolPage *deathptr; // Set each page to nil from back to back, i.e. delete each page do {deathptr = page; page = page->parent; if (page) { page->unprotect(); page->child = nil; page->protect(); } delete deathptr; } while (deathptr ! = this); }Copy the code

Description:

  • So this is deleting every page, back to front
  • The way to delete it is to set it to nil.

2.4.5 summary

  • Automatic release pool deletion refers to the deletion of pages from the last page to the previous page. If the last page is deleted, the previous page will be set as the current page. If the pool is deleted until there is no current page, the pool is empty
  • The deletion of the page is mainly the release of the object, which is also performed from back to front one objC_release until the boundary object is released