IOS underlying principles + reverse article summary

This article mainly analyzes the underlying implementation of AutoReleasePool and NSRunLoop

AutoReleasePool Automatically releases the pool

Automatic release poolIt’s one of the OC’sAutomatic memory reclamation mechanismWhich can be added to the AutoreleasePoolDelay the release of the variableIn simple terms, when you create aobjectUnder normal circumstances, variables are released immediately when they are out of scope. If an object is added to the automatic release pool, it does notWill not be released immediatelyWill,Wait until runloop is dormant/out of autoReleasepool scope {}beforeBe released. The mechanism is shown in the following figure

  • 1. From the start of the program to the completion of loading, the runloop corresponding to the main thread will be in hibernation state, waiting for user interaction to wake up the Runloop

  • 2. Each user interaction will launch a Runloop to process all the user’s clicks, touches, and so on

  • 3. When the Runloop listens to an interaction event, it creates an automatic release pool and adds any delayed objects to the automatic release pool

  • 4. Before a complete runloop ends, a release message is sent to all objects in the automatic release pool, and then the automatic release pool is destroyed

Clang analysis

Based on previous source code analysis experience, we first through clang analysis

  • Define the following code
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
}
Copy the code
  • To compile the underlying implementation through clang, the command is:xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m
Struct __AtAutoreleasePool {// constructor __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } // Destructor ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; }; Int main(int argc, const char * argv[]) {{__AtAutoreleasePool __autoreleasepool; } return 0; }Copy the code

In short, the automatic release pool is essentially an object

@autoreleasepool {} // Equivalent to {__AtAutoreleasePool __autoreleasepool; }Copy the code
  • __AtAutoreleasePool is a structure that has a constructor and a destructor that is called automatically when the object defined by the structure ends its scope

  • Where {} is the scope, the advantage is clear structure, readable, can be created and destroyed in time

The timing of the constructor and destructor calls involved can be demonstrated by the following example

struct CJLTest{ CJLTest (){ printf("1123 - %s\n", __func__); } ~CJLTest(){ printf("5667 - %s\n", __func__); }}; int main(int argc, const char * argv[]) { { CJLTest test; }} / / * * * * * * * * * * operation result * * * * * * * * * * 5667-1123 - CJLTest ~ CJLTestCopy the code

As a result, the constructor is automatically called when the object is created by CJLTest, and the destructor is automatically called when the object is out of the {} scope

Assembly analysis

  • Put a breakpoint in the main section of the code, run the program, and enable assembly debugging

The debugging results confirm the results of our Clang analysis

conclusion

  • Autoreleasepool is a structured object. An autoreleasepool object is a page, which is stored in a stack structure. It complies with the principle of first in, then out

  • The bottom of the stack of pages is an empty placeholder of 56 bytes for a total page size of 4096 bytes

  • Only the first page has sentinel objects, storing a maximum of 504 objects, and 505 objects from the second page

  • The objc_autoreleasePoolPush method is called when the autoreleasepool joins the object to be freed

  • The internal implementation of autoreleasepool is to call the objc_autoreleasePoolPop method when the destructor is called torelease

Analysis of the underlying

In the objC source code, AutoreleasePool is explained as follows

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 either an object to be freed, or POOL_BOUNDARY, which automatically frees the 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. A pool token is a pointer to the POOL_BOUNDARY for the pool. After the pool is ejected, each object that is 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 two-way 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 a hot page that stores newly automatically released objects.Copy the code

Through the description, there are the following points

  • Automatic release pool is a stack structure of Pointers

  • 2. The pointer to the object to be freed or pool_boundary sentinel (now often referred to as the boundary)

  • The automatic release pool is a page structure (mentioned in virtual memory), and the page is a two-way list (meaning that there are parents and children, mentioned in the class, that is, the inheritance chain of the class).

  • Automatic pool release is related to threads

For the automatic release pool, we are mainly concerned about the following three points:

  • 1. When is the automatic release pool created?

  • How are objects added to the automatic release pool?

  • 3. Which objects will be added to the automatic release pool?

With these questions in mind, let’s explore the underlying principles of automatic release pools step by step

AutoreleasePoolPage

  • From the initialclangorassemblyAnalysis we learned that the underlying automatic release pool is calledobjc_autoreleasePoolPushandobjc_autoreleasePoolPopTwo methods, whose source code implementation is as follows
/ / * * * * * * * * * * * push method * * * * * * * * * * * void * objc_autoreleasePoolPush (void) {return AutoreleasePoolPage: : push (); } / / * * * * * * * * * * * pop method * * * * * * * * * * * void objc_autoreleasePoolPop (void * CTXT) {AutoreleasePoolPage: : pop (CTXT); }Copy the code
  • We can see from the source code, are calledAutoreleasePoolPagethepushandpopThe following is the definition, from which you can see that the automatic release pool is both a page and a pageobject, the page size is4096byte
//************ 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; 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: ... // AutoreleasePoolPage(AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(),// start location objc_thread_self(),// newParent, newParent? 1+newParent->depth: 0, 1+newParent->depth: 0, 1+newParent? newParent->hiwat : 0) {... } // Destructor ~AutoreleasePoolPage() {... }... Id * begin() {... } id * end() {... Bool empty() {... Bool full() {... Bool lessThanHalfFull() {... *add(id obj){... } // releaseAll objects void releaseAll() {... Void releaseUntil(id *stop) {... } void kill() {... } static void tls_dealloc(void *p) {... Static AutoreleasePoolPage *pageForPointer(const void *p) {... } static AutoreleasePoolPage *pageForPointer(uintptr_t p) {... Static inline bool haveEmptyPoolPlaceholder() {... Static inline ID * setEmptyPoolPlaceholder(){... Static Inline AutoreleasePoolPage *hotPage(){... } static inline void setHotPage(AutoreleasePoolPage *page) {... Static inline AutoreleasePoolPage *coldPage() {... } static inline id *autoreleaseFast(id obj){... } // Add automatic release object, Static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {... } static __attribute__((noinline)) id *autoreleaseNoPage(id obj){... Static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {... } public: static inline ID autorelease(id obj){... Static inline void *push() {... } 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
  • From its definition,AutoreleasePoolPageIs inherited fromAutoreleasePoolPageDataAnd the properties of this class are also from the parent classAutoreleasePoolPageDataThe definition,
    • Found that there wereAutoreleasePoolPageObject, so there is the following chain of relationshipsAutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPageFrom here, you can tell whether the automatic release pool is a page or a pageTwo-way linked liststructure
class AutoreleasePoolPage; Struct AutoreleasePoolPageData {// Used to verify the structure of AutoreleasePoolPage magic_t const magic; //16 bytes // points to the next location of the newly added autoreleased object, initialized to begin() __unsafe_unretained ID *next; Pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; AutoreleasePoolPage *child; AutoreleasePoolPage *child; // uint32_t const depth; // Uint32_t const depth // Uint32_t hiwat; // Uint32_t hiwat; AutoreleasePoolPageData(__unsafe_unretained ID * _next, pthread_t _thread, AutoreleasePoolPage* _parent, __unsafe_unretained ID * _next, pthread_t _thread) uint32_t _depth, uint32_t _hiwat) : magic(), next(_next), thread(_thread), parent(_parent), child(nil), depth(_depth), hiwat(_hiwat) { } };Copy the code

The AutoreleasePoolPageData structure has a memory size of 56 bytes:

  • The magic property is of type MAGic_t structure and has a memory size of M [4]. Memory occupied (i.e. 4*4=16 bytes)

  • Properties next (pointer), Thread (object), parent (object), and Child (object) are all 8 bytes (4*8=32 bytes).

  • The attributes depth and hiwat are of type uint32_t, and the actual type is unsigned int. Both are 4 bytes (2*4=8 bytes)

Objc_autoreleasePoolPush source analysis

Into the push source implementation, there is the following logic

  • Check whether the pool exists

  • If not, it is created through the autoreleaseNewPage method

  • If so, the sentinel object is pressed via autoreleaseFast

Static inline void *push() {id *dest; If (slowPath (DebugPoolAllocation)) {Each autoRelease pool starts on a new pool page. // If not, create dest = autoreleaseNewPage(POOL_BOUNDARY); } else {// hold down a POOL_BOUNDARY, i.e. } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }Copy the code

Create autoreleaseNewPage

  • Enter theObjc_autoreleasePoolPush -> push -> autoreleaseNewPage source code implementation, mainly throughHotPage ‘obtains the current page and determines whether the current page exists
    • If so, the object is pressed through the autoreleaseFullPage method

    • If it does not exist, the page is created through the autoreleaseNoPage method

Static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {// Get AutoreleasePoolPage *page = hotPage(); If (page) return autoreleaseFullPage(obj, page); if (page) return autoreleaseFullPage(obj, page); Else return autoreleaseNoPage(obj); else return autoreleaseNoPage(obj); } //******** hotPage method ******** // Obtain the current operation page static Inline AutoreleasePoolPage *hotPage() {// Obtain the AutoreleasePoolPage of the current page *result = (AutoreleasePoolPage *) tls_get_direct(key); If ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; } //******** autoreleaseNoPage method ******** static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { 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; // Check whether it is an empty placeholder. If it is, {// We are pushing a second pool over the empty 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; If (obj == POOL_BOUNDARY &&!) // If (obj == POOL_BOUNDARY &&! DebugPoolAllocation) {// 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. // Initialize the first page AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); // Set page to the current focus page. // Push a boundary on behalf of the previously-placeholder'd pool. If (pushExtraBoundary) {// pushExtraBoundary page->add(POOL_BOUNDARY); } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * }Copy the code

AutoreleaseNoPage = AutoreleasePoolPage = AutoreleasePoolPage = AutoreleasePoolPage = AutoreleasePoolPage = AutoreleasePoolPage = AutoreleasePoolPage The constructor is implemented by the parent AutoreleasePoolPageData initializer (as you can see from the above definition).

//**********AutoreleasePoolPage Constructor **********AutoreleasePoolPage (AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(),// start location objc_thread_self(),// newParent, newParent? 1+newParent->depth: 0, 1+newParent->depth: 0, 1+newParent? newParent->hiwat : 0) { if (parent) { parent->check(); ASSERT(! parent->child); parent->unprotect(); Parent ->child = this; parent->child = this; parent->child = this; parent->protect(); } protect(); } //**********AutoreleasePoolPageData initiality. **********AutoreleasePoolPageData (__unsafe_unretained ID * _next, but not retained) 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

The AutoreleasePoolPageData parameter meanings are as follows:

  • begin()saidThe pressure of stack(that is, the next pushdown address of the object to be released). You can debug it through the source codebeginAnd find that its concrete implementation is equal toPage top address +56, in which the56That’s the structureAutoreleasePoolPageDataMemory size of

/ / * * * * * * * * the begin () * * * * * * * * / / page starting position id * the begin () {/ / is equal to the first address + 56 (AutoreleasePoolPage memory size of the return (id *) ((uint8_t *)this+sizeof(*this)); }Copy the code
  • objc_thread_self()Represents theThe current threadWhile the current thread passestlsTo obtain the
__attribute__((const)) static inline pthread_t objc_thread_self() {// get the current thread return via TLS (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF); }Copy the code
  • NewParent indicates the parent node

  • The next two parameters are the depth of the parent node, the maximum number of pushes, and hiwat

View the memory structure of the automatic release pool

Because in ARC mode, it is not possible to call manuallyautorelease, so switch Demo to MRC mode (Build Settings -> Objectice-C Automatic Reference CountingSet toNO)

  • Define the following code
/ / * * * * * * * * * * * * printing automatic release of pool structure * * * * * * * * * * * * extern void _objc_autoreleasePoolPrint (void); / / * * * * * * * * * * * * run code * * * * * * * * * * * * int main (int arg c, const char * argv []) {@ autoreleasepool {/ / cycle to create objects, For (int I = 0; i < 5; i++) { NSObject *objc = [[NSObject alloc] sutorelease]; } // Call _objc_autoreleasePoolPrint(); }}Copy the code

When we run this, we find 6 objects, but we only have 5 objects to pushPOOLsaidThe sentry, i.e.,The border, whose purpose is toTo prevent crossing the line Looking at the memory structure of the automatic release pool, it is found that the first address of the page is withThe sentry objectDifference between0x38To decimal is exactly56, that is,AutoreleasePoolPageThe size of its own memory

  • Change the data from the above test code to505, its memory structure is as follows, found the first page full, stored504The second page stores only one object to be freed

  • Change the data to505 + 506To verify that the second page also stores 504 objects

By running discovery, the first page is stored504, the second page storage505, the third page storage2a

conclusion

Therefore, through the above tests, the following conclusions can be drawn:

  • The first page can hold 504 objects, and only the first page has a sentinel. When a page is full, a new page is opened

  • At the beginning of the second page, you can hold up to 505 objects

  • The size of a page is equal to 505 * 8 = 4040

The same can be saidAutoreleasePoolPageIn theSIZEFrom the definition we can say that the size of a page is4096Byte, and in its constructorObject pressing positionFrom theThe first address + 56It starts so that one page can actually be stored4096-56 = 4040 bytesIs converted to an object4040/8 is 505One page at mostStore 505 objects, includingThe first page has sentinel objectsCan only be stored504A. Its structure is illustrated below

Interview question: How many sentinels are there in an automatic release pool?

  • There is only one sentinel object, and the sentinel is on the first page

  • The first page can hold a maximum of 504 objects, and the second page can hold a maximum of 505 objects

2. Push object autoreleaseFast

  • Enter theautoreleaseFastSource code, there are mainly the following steps:
    • Gets the current action page and determines whether the page exists and is full

    • If the page exists and is not full, the object is pressed through the add method

    • If the page exists and is full, arrange a new page through the autoreleaseFullPage method

    • If the page does not exist, a new page is created using the autoreleaseNoPage method

Static inline ID *autoreleaseFast(id obj) {AutoreleasePoolPage *page = hotPage(); If (page &&! Return page->add(obj); return page->add(obj); } else if (page) {return autoreleaseFullPage(obj, page);} else if (page) {return autoreleaseFullPage(obj, page); } else {// Create a new page if the page does not exist return autoreleaseNoPage(obj); }}Copy the code
AutoreleaseFullPage method

This method is mainly used to determine whether the current page is full, if the current page is full, through the do-while loop to find the page corresponding to the child node, if not, create a page, and the stack object

// Add automatic release object, Call this method 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; Else Page = new AutoreleasePoolPage(page); else Page = new AutoreleasePoolPage(page); } while (page->full()); // Set to the current operation page setHotPage(page); Return page->add(obj); }Copy the code

The AutoreleasePoolPage initialization method is used to manipulate the Child object to point the child of the current page to the new page, indicating that the page is connected by a two-way list

The add method

This method mainly adds the release object, the underlying implementation is to store the release object through the next pointer, and the next pointer is incremented, indicating the next release object store location. You can see here that pages are stored in a stack structure

*add(id obj) {ASSERT(! full()); unprotect(); // pass in the location of the object store id *ret = next; // faster than 'return next-1' because of aliasing // faster than 'return next-1' because of aliasing // faster than 'next' *next++ = obj; protect(); return ret; }Copy the code

3. Autorelease underlying analysis

In the demo, we used the autorelease method to push objects into the automatic release pool in MRC mode. Let’s examine the underlying implementation

  • To viewautoreleaseMethods the source code
    • If it is not an object or a small object, it is returned directly
    • If it is an object, the object’sautoreleaseTo release
__attribute__((aligned(16), Flatten, noinline)) id objc_autoRelease (id obj) {// If it is not an object, return if (! obj) return obj; If (obj->isTaggedPointer()) return obj; return obj->autorelease(); }Copy the code
  • Into the objectautoreleaseimplementation
πŸ‘‡ 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

The autoreleaseFast method is used for both sentinel and normal objects, but the difference is identified

Objc_autoreleasePoolPop source analysis

In the objc_autoreleasePoolPop method, there is a parameter, in clang analysis, found that the passed parameter is the sentinel object returned after push, namely CTXT, its purpose is to avoid stack chaos, to prevent other objects out of the stack

  • Enter thepopSource code implementation, mainly by the following steps
    • The blank page is processed and the page is obtained based on the token

    • Fault-tolerant processing

    • Out of the stack page by popPage

Static inline void pop(void *token) {AutoreleasePoolPage *page; id *stop; If (token == (void*)EMPTY_POOL_PLACEHOLDER) {// Placeholder is empty before Popping the top level pool. // Get the current page page = hotPage(); if (! Page) {// Pool was never used. Clear the placeholder. // Return setHotPage(nil) if the current page does not exist; } Pool was used. Pop its contents normally. // Pool pages remain reserved for re-use as usual. The current page is set to coldPage and the token is set to the starting position of coldPage page = coldPage(); token = page->begin(); } else {// Get the token page = pageForPointer(token); } stop = (id *)token; If (*stop! = POOL_BOUNDARY) {if (stop == page->begin() &&! Page ->parent) {// if it is the first position and there is no parent, do nothing // Start of 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 {// If it is the first place and has a parent, 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
  • Enter thepopPageSource code, which is passed inallowDebugIf the value is false, it passesreleaseUntilOff stack current pagestopAll objects before the position, that is, objects in the stackSending a Release message, until it encounters the incomingThe sentry object
Template <bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); Page ->releaseUntil(stop); If (allowDebug && DebugPoolAllocation && page->empty()) {// special case: Delete everything during page-per-pool debugging // Get AutoreleasePoolPage *parent = AutoreleasePoolPage during page-per-pool debugging // Get AutoreleasePoolPage *parent = AutoreleasePoolPage during page-per-pool debugging page->parent; Page ->kill(); // Set the action page to the parent page. SetHotPage (parent); } else if (allowDebug && DebugMissingPools && page->empty() && ! page->parent) { // special case: Delete everything for pop(top) // When debugging missing autorelease pools // Delete all contents of pop (top) when debugging lost automatic release pool 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
  • Enter thereleaseUntilRealization, mainly throughTo iterate over, to determine whether the object is equal to stop, whose purpose isBefore releasing stopOf all objects,
    • We first release the object (the last object in page) by getting next of Page, and decrement next to get the last object

    • Check if it is a sentinel object. If it is not, automatically call objc_release to release it

Void releaseUntil(id *stop) {// Not recursive: We don't want to blow out the stack is not recursive: // If a thread accumulates a stupendous amount of garbage // Determine if the next object is stop. If not, While (this->next! = stop) {// Restart from hotPage() every time, in case-release // Autorelease more objects from hotPage() every time, AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, While (page->empty()) {// set page = page->parent; // set page = page->parent; // Set the current page to the parent 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
  • Enter thekillImplementation, primarily to destroy the current page, willThe current page is assigned to the parent pageAnd willThe child object pointer to the parent page is set to nil
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; do { deathptr = page; Page = page->parent; if (page) { page->unprotect(); Page ->child = nil; page->protect(); } delete deathptr; } while (deathptr ! = this); }Copy the code

conclusion

Based on the above analysis, push and POP of automatic release pool are summarized as follows

  • In the automatic release poolThe pressure of stack(i.e.push) in the operation
    • When there is no pool, that is, only empty placeholders (stored in TLS), the page is created,Press the sentry object
    • In the pagePressing common objectsMainly throughnextPointer to theincreasing,
    • whenPage fullWhen, you need to set the pagechildThe object ofA new page

So, to sum up,autoreleaseandobjc_autoreleasePushThe overall underlying process is shown in the following figure

  • In the automatic release poolOut of the stack(i.e.pop) in the operation
    • In the pageOffstack common objectsMainly throughnextPointer to thediminishing,
    • whenEmpty pageWhen, need to assign the pageparentThe object ofThe current page

To sum up,objc_autoreleasePoolPopThe following figure shows the process of exiting the stack

RunLoop

With RunLoop, the main points of interest are as follows

  • 1. What is runloop?

  • What is the relationship between runloop and thread?

  • 3. When was Runloop created?

1. RunLoop introduction

RunLoop is an implementation of the event receiving and distributing mechanism. It is part of the thread-dependent infrastructure. A RunLoop is an event processing loop that is used to continuously schedule work and process input events.

RunLoop is essentially a do-while loop, where you rest when you have nothing to do and work when you come to work. This is different from a normal while loop, which causes the CPU to go into a busy wait state, which consumes CPU all the time. A RunLoop does not. A RunLoop is a idle wait state, which means a RunLoop has hibernation.

The role of the RunLoop

  • Keep the program running

  • Handle various events in your App (touch, timer, performSelector)

  • Save CPU resources, provide program performance, do things and take breaks

RunLoop source analysis

RunLoop source code download address, where you can find the latest version to download

2. Relationship between RunLoop and thread

In general, there are two ways to obtain RunLoop in daily development

CFRunLoopRef mainRunloop = CFRunLoopGetMain(); // Current running loop CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();Copy the code
  • Enter theCFRunLoopGetMainThe source code
CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed //pthread_main_thread_np main thread if (! __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; }Copy the code
  • Enter the_CFRunLoopGet0
// should only be called by Foundation // t==0 is a synonym for "main thread" that always works CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {// Mark the main thread if t does not exist (the default, If (pthread_equal(t, kNilPthreadT)) {t = pthread_main_thread_np(); } __CFSpinLock(&loopsLock); if (! __CFRunLoops) { __CFSpinUnlock(&loopsLock); // Create a global dictionary, Marked as kCFAllocatorSystemDefault CFMutableDictionaryRef dict = CFDictionaryCreateMutable (kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); Dict (" thread ", "runloop", "thread", "runloop", "thread", "runloop") key value CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFSpinLock(&loopsLock); } // Get runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFSpinUnlock(&loopsLock); if (! Loop) {// If not, create a new run loop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (! Loop) {// bind the new runLoop to the thread key-value (__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }Copy the code

There are only two types of Runloop, one for the main thread and one for other threads. That is, runloops and threads correspond one to one

3. Creation of RunLoop

  • Enter the__CFRunLoopCreateSource code, which is mainly on the runloop attribute assignment operation
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) { CFRunLoopRef loop = NULL; CFRunLoopModeRef rlm; uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase); loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL); If (NULL == loop) {return NULL; } // Configure the runloop property (void)__CFRunLoopPushPerRunData(loop); __CFRunLoopLockInit(&loop->_lock); loop->_wakeUpPort = __CFPortAllocate(); if (CFPORT_NULL == loop->_wakeUpPort) HALT; __CFRunLoopSetIgnoreWakeUps(loop); loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode); loop->_commonModeItems = NULL; loop->_currentMode = NULL; loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); loop->_blocks_head = NULL; loop->_blocks_tail = NULL; loop->_counterpart = NULL; loop->_pthread = t; #if DEPLOYMENT_TARGET_WINDOWS loop->_winthread = GetCurrentThreadId(); #else loop->_winthread = 0; #endif rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true); if (NULL ! = rlm) __CFRunLoopModeUnlock(rlm); return loop; }Copy the code
  • Enter theCFRunLoopRefThe definition of theta, according to the definition, in factA RunLoop is also an object. is__CFRunLoopStructure of thePointer types
typedef struct __CFRunLoop * CFRunLoopRef; πŸ‘‡ struct __CFRunLoop {CFRuntimeBase _base; pthread_mutex_t _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // used for CFRunLoopWakeUp Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; uint32_t _winthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFTypeRef _counterpart; };Copy the code

And from the definition,A RunLoop relies on multiple modes, means that a RunLoop needs to process multiple transactions, i.eOne Mode corresponds to multiple items, and an item contains the timer, source, and observer, as shown below

Mode there are five modes mentioned in the Apple documentation, but only NSDefaultRunLoopMode and NSRunLoopCommonModes are publicly exposed in iOS. NSRunLoopCommonModes is actually a collection of the Mode, including NSDefaultRunLoopMode and NSEventTrackingRunLoopMode by default.

  • NSDefaultRunLoopMode: Default mode, which is the normal mode

  • NSConnectionReplyMode

  • NSModalPanelRunLoopMode

  • NSEventTrackingRunLoopMode: use this Mode to track events from the user interaction (such as a UITableView slide up and down)

  • NSRunLoopCommonModes: Pseudo-modes, more flexibility

Source & Timer & Observer

  • Source represents events that can wake up a RunLoop. For example, when a user clicks on the screen, a RunLoop is created. The main categories are Source0 and Source1

    • Source0 indicates non-system events, that is, user-defined events

    • Source1 indicates the system event, which is mainly responsible for the underlying communication and has the wake-up capability

  • A Timer is a common type of NSTimer Timer

  • The Observer is used to monitor and respond to the status changes of the RunLoop

Typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {// Enter RunLoop kCFRunLoopEntry = (1UL << 0), // Timers kCFRunLoopBeforeTimers = (1UL << 1), Source kCFRunLoopBeforeSources = (1UL << 2), KCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), RunLoop kCFRunLoopExit = (1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU};Copy the code

Verify: RunLoop and mode are one-to-many. Next, verify the relationship we mentioned above with the code debugging above

  • Run the LLDB command to obtain the valuemainRunloop,currentRunloopthecurrentMode
    • po CFRunLoopCopyCurrentMode(mainRunloop)
    • po CFRunLoopCopyCurrentMode(currentRunloop)

And from there, it says,Runloop has only one mode at runtime

  • To obtainmainRunloopOf all models, i.epo CFRunLoopCopyAllModes(mainRunloop)

It can be verified from the resultsrunloop ε’Œ CFRunloopModewithMore than a pair ofThe relationship between

Verify: Mode and Item are also one-to-many

  • In the above code, add a breakpoint and look at the stack information through BT. From here, you can see that the item type of the timer is as follows

  • inRunLoopView in source codeItemTypes, there are the following
    • Block application: __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

    • Call timer: __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

    • Response source0: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

    • In response to source1: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

    • GCD Main queue :__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

    • The observer source: __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

  • Here toThe Timer for exampleGenerally, the timer is passed when the timer is initializedaddTimer:forMode:Method is added to Runloop, so look it up in the source codeaddTimer, i.eCFRunLoopAddTimerMethod, its source code implementation is as follows, its implementation is mainly judgedkCFRunLoopCommonModes, and then find the mode of the runloop for matching processing
    • Among themkCFRunLoopCommonModesNot a pattern, but an abstractionPseudo modeWhich is more flexible than defaultMode
    • throughCFSetAddValue(rl->_commonModeItems, rlt);We know that,runloopwithmode 是More than a pair ofAt the same time, you can getmode 与 itemIs alsoMore than a pair ofthe
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) { CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return; if (! __CFIsValid(rlt) || (NULL ! = rlt->_runLoop && rlt->_runLoop ! = rl)) return; __CFRunLoopLock(rl); / / key points: KCFRunLoopCommonModes if (modeName == kCFRunLoopCommonModes) {// If the type is kCFRunLoopCommonModes CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; if (NULL == rl->_commonModeItems) { rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); } //runloop and mode are one to many, mode and item are one to many CFSetAddValue(rl->_commonModeItems, RLT); if (NULL ! = set) { CFTypeRef context[2] = {rl, rlt}; / * the add new item to all common modes * / / / execution CFSetApplyFunction (set, (__CFRunLoopAddItemToCommonModes), (void *)context); CFRelease(set); } else {// If the type is non-CommonMode // find the model of runloop CFRunLoopModeRef RLM = __CFRunLoopFindMode(rl, modeName, true); // If the type is non-CommonMode, find the model of runloop CFRunLoopModeRef RLM = __CFRunLoopFindMode(rl, modeName, true); if (NULL ! = rlm) { if (NULL == rlm->_timers) { CFArrayCallBacks cb = kCFTypeArrayCallBacks; cb.equal = NULL; rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); } // If (NULL! = rlm && ! CFSetContainsValue(rlt->_rlModes, rlm->_name)) { __CFRunLoopTimerLock(rlt); if (NULL == rlt->_runLoop) { rlt->_runLoop = rl; } else if (rl ! = rlt->_runLoop) { __CFRunLoopTimerUnlock(rlt); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); return; [runloop run] CFSetAddValue(RLT ->_rlModes, RLM ->_name); [runloop run] CFSetAddValue(RLT ->_rlModes, RLM ->_name); __CFRunLoopTimerUnlock(rlt); __CFRunLoopTimerFireTSRLock(); __CFRepositionTimerInMode(rlm, rlt, false); __CFRunLoopTimerFireTSRUnlock(); if (! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) { // Normally we don't do this on behalf of clients, but for // backwards compatibility due to the change in timer handling... if (rl ! = CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); } } if (NULL ! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code

4. RunLoop execution

It is well known that the execution of RunLoop relies onrunMethod, as you can see from the following stack information, its underlying execution is__CFRunLoopRunmethods

  • Enter the__CFRunLoopRunSource code, for different objects, have different processing
    • If there are observers, __CFRunLoopDoObservers is called

    • If there is a block, __CFRunLoopDoBlocks is called

    • If there is a timer, __CFRunLoopDoTimers is called

    • If it is source0, __CFRunLoopDoSources0 is called

    • If it is source1, __CFRunLoopDoSource1 is called

/* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { ... do{ ... / / notify Observers: Timer event if (RLM ->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers) is about to be processed; / / notify Observers: If (RLM ->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rL, RLM, kCFRunLoopBeforeSources); // Process Blocks __CFRunLoopDoBlocks(rl, RLM); SourceHandledThisLoop = __CFRunLoopDoSources0(rL, RLM, stopAfterHandle); // Processing sources0 returns YES if (sourceHandledThisLoop) {// processing Blocks __CFRunLoopDoBlocks(rL, RLM); }... // If it is timer else if (modeQueuePort! = MACH_PORT_NULL && livePort == modeQueuePort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); if (! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer, because we apparently fired early __CFArmNextTimerInMode(rlm, rl); }}... / / if it is source1 CFRunLoopSourceRef RLS = __CFRunLoopModeFindSourceForMachPort (rl, RLM, livePort); if (rls) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI mach_msg_header_t *reply = NULL; sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; if (NULL ! = reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply); } #elif DEPLOYMENT_TARGET_WINDOWS sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop; #endif } ... }while (0 == retVal); . }Copy the code
  • Enter the__CFRunLoopDoTimersSource code, mainly through the for loop, for a single timer processing
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */ ... For (CFIndex idx = 0, CNT = timers? CFArrayGetCount(timers) : 0; idx < cnt; idx++) { CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx); Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt); timerHandled = timerHandled || did; }... }Copy the code
  • Enter the__CFRunLoopDoTimerSource code, the main logic istimerAfter the execution is complete, it will actively call__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__Function, just as in the timer stack call

// mode and rl are locked on entry and exit static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { ... __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info); . }Copy the code

Timer Execution Summary

  • For the custom timer, set Mode and add it to RunLoop

  • When RunLoop’s run method executes, __CFRunLoopDoTimers is called to execute all timers

  • In the __CFRunLoopDoTimers method, a single timer is executed through a for loop

  • In the __CFRunLoopDoTimer method, after the timer completes, the corresponding timer callback function is executed

Above, is aimed attimerExecution analysis, forObserver, block, source0, and source1It works in a similar way to a timer, and I won’t repeat it hereOfficial Apple DocumentDiagrams of processing different sources for RunLoop

5. Underlying Principle of RunLoop

From the above stack information, it can be seen that the implementation path of run in the bottom layer is CFRunLoopRun -> CFRunLoopRun -> __CFRunLoopRun

  • Enter theCFRunLoopRunSource code, where the parameters passed in1.0 e10This is equal to 1 times e to the tenth, which is used to denotetimeout
void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; Do {// 1.0e10: 1*10^10 result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code
  • Enter theCFRunLoopRunSpecificSource code, first according to modeName to find the corresponding mode, and then mainly divided into three cases:
    • If it is an Entry, the observer is notified that the runloop is about to enter

    • If exit, the Runloop is about to exit through the observer

    • For other intermediate states, the various sources are handled primarily through runloop

Its pseudocode is represented as follows

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); CFRunLoopModeRef currentMode = cfrunloopFindMode (rl, modeName, false); // Inform Observers: RunLoop that they are about to enter loop. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); / / internal function, into the loop result = __CFRunLoopRun (rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // Inform Observers: RunLoop that they are about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; }Copy the code
  • Enter the__CFRunLoopRunSource code, because this part of the code is more, so here with pseudocode instead. The main logic isDifferent processing is performed according to different event sourcesWhen the RunLoop is dormant, the RunLoop can be awakened by the corresponding event
/* rl, RLM are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef RLM, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){// Start a timer by GCD, Then start running laps dispatch_sourCE_t timeout_timer = NULL; . dispatch_resume(timeout_timer); int32_t retVal = 0; Do {// Inform Observers: About to handle timer events __CFRunLoopDoObservers(rL, RLM, kCFRunLoopBeforeTimers); / / notify Observers: To handle Source event __CFRunLoopDoObservers(rL, RLM, kCFRunLoopBeforeSources) // to handle Blocks __CFRunLoopDoBlocks(rL, RLM); SourceHandledThisLoop = __CFRunLoopDoSources0(rL, RLM, stopAfterHandle); // Processing sources0 returns YES if (sourceHandledThisLoop) {// processing Blocks __CFRunLoopDoBlocks(rL, RLM); } / / judge whether port (Source1) if (__CFRunLoopWaitForMultipleObjects (NULL, & dispatchPort, 0, 0, & livePort, NULL)) {goto handle_msg; } // Inform Observers: about to hibernate __CFRunLoopDoObservers(rL, RLM, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll? 0 : TIMEOUT_INFINITY); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); // Inform Observers: Be alert, and end hibernation __CFRunLoopDoObservers(rL, RLM, kCFRunLoopAfterWaiting); Handle_msg: if (awakened by timer) {// process Timers __CFRunLoopDoTimers(rL, RLM, mach_absolute_time()); }else if (wake up by GCD){// process GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); }else if (awaken by source1){// awaken by source1, Source1 __CFRunLoopDoSource1(rl, RLM, RLS, MSG, MSG ->msgh_size, &reply)} if (sourceHandledThisLoop && stopAfterHandle) { retVal = kCFRunLoopRunHandledSource; Else if (timeout_context->termTSR < mach_absolute_time()) {retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rL)) {__CFRunLoopUnsetStopped(rL); retVal = kCFRunLoopRunStopped; Else if (RLM ->_stopped) {RLM ->_stopped = false; retVal = kCFRunLoopRunStopped; Else if (__CFRunLoopModeIsEmpty(rl, RLM, previousMode)) {retVal = kCFRunLoopRunFinished;} else if (__CFRunLoopModeIsEmpty(rl, RLM, previousMode)) {retVal = kCFRunLoopRunFinished; }}while (0 == retVal); return retVal; }Copy the code

So, to sum up,RunLoopThe execution process is as follows

Related Interview Questions

AutoreleasePool related

Interview Question 1: When are temporary variables released?

  • If, under normal circumstances, it is out of scope, it will be released immediately

  • If temporary variables are added to the automatic releasepool, release is delayed, i.e. after the runloop goes to sleep or the Autoreleasepool scope

Interview question 2: How AutoreleasePool works

  • The essence of an AutoreleasePoolPage is an AutoreleasePoolPage object, which is a stack of pages. Each AutoreleasePoolPage is connected in the form of a two-way linked list

  • Automatic release of a pool’s push and exit is done by calling the underlying objc_autoreleasePoolPush and objc_autoreleasePoolPop constructors and destructors. The AutoreleasePoolPage push and pop methods are actually called

  • A new AutoreleasePoolPage is created each time the AutoreleasePoolPage is called. The AutoreleasePoolPage inserts a POOL_BOUNDARY and returns the memory address where the POOL_BOUNDARY was inserted. While push calls autoreleaseFast method internally, there are mainly the following three cases

    • When the page exists and is not, the add method is called to add the object to the next pointer to the page, incrementing next

    • When the page exists and is full, a call to autoreleaseFullPage initializes a new page, and then the add method is called to add the object to the page stack

    • When the page does not exist, the autoreleaseNoPage is called to create a hotPage, and then the add method is called to add the object to the page stack

  • When the POP operation is performed, a value is passed in. This value is the return value of the push operation, which is the memory address token of POOL_BOUNDARY. So the internal implementation of POP is to find the sentinel object on the page based on the token, and then use objc_release to release the object before the token and put the next pointer to the correct location

Interview Question 3: Can AutoreleasePool be nested?

  • Can be nested, the purpose of which is to control the peak memory of an application so that it is not too high

  • Nesting is possible because automatic release pools are stacked as nodes, connected in a two-way linked list, and one to one with threads

  • The multi-layer nesting of the automatic release pool is essentially a continuous release of pushs sentinel objects. When pop is released, the inner ones are first released and the outer ones are released

Interview Question 4: Who can join AutoreleasePool? Is alloc ok?

  • Objects generated using the new, alloc, or copy keywords and retained objects need to be manually released and will not be added to the automatic release pool

  • Objects set to autoRelease do not need to be manually freed; they go directly to the automatic release pool

  • All autorelease objects, once out of scope, are automatically added to the recently created autorelease pool

Question 5: When will AutoreleasePool be released?

  • The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler ().

  • The first event monitored by the Observer is an Entry(about to enter the Loop), which calls _objc_autoreleasePoolPush() within its callback to create an automatic release pool. Its order is -2147483647, which has the highest priority, ensuring that the create release pool occurs before all other callbacks.

  • The second Observer monitors two events: BeforeWaiting(ready to go to sleep) calls _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() releases old pools and creates new ones; BeforeWaiting(ready to go to sleep) calls _objc_autoreleasePoolPush() releases old pools and creates new pools; Call _objc_autoreleasePoolPop() when Exit(that is, to Exit the Loop) torelease the automatic release pool. The order of this Observer is 2147483647, which has the lowest priority, ensuring that its pool release occurs after all other callbacks.

Interview Question 6: The relationship between Thread and AutoreleasePool

In the official documentation, find the following instructions

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools  are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.Copy the code

The general meaning is as follows:

  • Each thread, including the main thread, maintains its own automatic release pool stack structure

  • New automatic release pools are added to the top of the stack when they are created. It is removed from the stack when the pool destruction is automatically released

  • For the current thread, automatically released objects are placed on the top of the stack in the automatically released pool; When a thread stops, all automatic release pools associated with that thread are automatically released

Summary: Each thread has an automatic pool release stack structure associated with it. When a new pool is created, it is pushed to the top of the stack, when the pool is destroyed, it is removed from the stack, for the current thread, the release object is pushed to the top of the stack, and when the thread stops, it is automatically released

The relationship between RunLoop and AutoreleasePool is described in the official document

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.
Copy the code

The general meaning is as follows:

  • The RunLoop in the main program automatically creates an autoreleasePool before each event loop

  • At the end of the event loop, the drain operation is performed to release the objects

RunLoop related

The interview questions 1

We have a child thread, and we have a timer in that child thread. Can the Timer execute and print continuously?

CJLThread *thread = [[CJLThread alloc] initWithBlock:^{// thread.name = nil because this variable is just catching // CJLThread *thread = nil // Thread = Initialize to catch a nil in NSLog(@"%@-- %@",[NSThread currentThread],[[NSThread currentThread] name]); [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"hello word"); // If (self.isstopping) {[NSThread exit];}}];}]; thread.name = @"lgcode.com"; [thread start];Copy the code
  • No, becauseThe runloop of the child thread is not started by default, you need torunloop runTo start, change the above code to the following:
CJLThread *thread = [[CJLThread alloc] initWithBlock:^{// thread.name = nil because this variable is just catching // CJLThread *thread = nil NSLog(@"%@-- %@",[NSThread currentThread],[[NSThread currentThread] name]); [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"hello word"); // If (self.isstopping) {[NSThread exit];}}]; [[NSRunLoop currentRunLoop] run];}]; thread.name = @"lgcode.com"; [thread start];Copy the code

Interview Question 2: The relationship between RunLoop and thread

  • Each thread has a corresponding RunLoop, so a RunLoop corresponds to a thread one to one, and its binding is stored in a global DIctionary, with thread as key and RunLoop as value.

  • The RunLoop in the thread is mainly used to manage the thread. When the RunLoop of the thread is started, it will go to sleep after completing tasks. When an event triggers a wake up, it will start to work again

  • The RunLoop on the main thread is enabled by default and will run without exiting after the program starts

  • The RunLoop function of other threads is disabled by default. You can manually enable it if necessary

Interview 3: Difference between NSRunLoop and CFRunLoopRef

  • NSRunLoop is based on the CFRunLoopRef object-oriented API and is not secure

  • CFRunLoopRef is based on the C language and is thread safe

Interview 4: What is the mode function of Runloop?

Mode is used to specify the priority of events in a RunLoop

5: the interview with + scheduledTimerWithTimeInterval: way to trigger the timer, when sliding on the page list, the timer will suspend the callback, why? How to solve it?

  • The reason the timer stops is because when you slide the scrollView, the RunLoop of the main thread switches from NSDefaultRunLoopMode to UITrackingRunLoopMode, and the timer is added to NSDefaultRunLoopMode. So the timer is not going to execute

  • Execute the timer in NSRunLoopCommonModes

Above explanation, all for personal understanding, if insufficient, please leave a message to add, thank you