IOS underlying principles + reverse article summary
This article mainly analyzes the underlying implementation of AutoReleasePool and NSRunLoop
AutoReleasePool Automatically releases the pool
Automatic release pool
It’s one of the OC’sAutomatic memory reclamation mechanism
Which can be added to the AutoreleasePoolDelay the release of the variable
In simple terms, when you create aobject
Under 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 immediately
Will,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 initial
clang
orassembly
Analysis we learned that the underlying automatic release pool is calledobjc_autoreleasePoolPush
andobjc_autoreleasePoolPop
Two 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 called
AutoreleasePoolPage
thepush
andpop
The following is the definition, from which you can see that the automatic release pool is both a page and a pageobject
, the page size is4096
byte
//************ 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,
AutoreleasePoolPage
Is inherited fromAutoreleasePoolPageData
And the properties of this class are also from the parent classAutoreleasePoolPageData
The definition,- Found that there were
AutoreleasePoolPage
Object, so there is the following chain of relationshipsAutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage
From here, you can tell whether the automatic release pool is a page or a pageTwo-way linked list
structure
- Found that there were
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 the
Objc_autoreleasePoolPush -> push -> autoreleaseNewPage source code implementation, mainly through
HotPage ‘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 codebegin
And find that its concrete implementation is equal toPage top address +56
, in which the56
That’s the structureAutoreleasePoolPageData
Memory 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 thread
While the current thread passestls
To 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 Counting
Set 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 pushPOOL
saidThe 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 object
Difference between0x38
To decimal is exactly56
, that is,AutoreleasePoolPage
The size of its own memory
- Change the data from the above test code to
505
, its memory structure is as follows, found the first page full, stored504
The second page stores only one object to be freed
- Change the data to
505 + 506
To verify that the second page also stores 504 objects
By running discovery, the first page is stored504
, the second page storage505
, the third page storage2
a
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 saidAutoreleasePoolPage
In theSIZE
From the definition we can say that the size of a page is4096
Byte, and in its constructorObject pressing position
From theThe first address + 56
It starts so that one page can actually be stored4096-56 = 4040 bytes
Is converted to an object4040/8 is 505
One page at mostStore 505 objects
, includingThe first page has sentinel objects
Can only be stored504
A. 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 the
autoreleaseFast
Source 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 view
autorelease
Methods the source code- If it is not an object or a small object, it is returned directly
- If it is an object, the object’s
autorelease
To 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 object
autorelease
implementation
π 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 the
pop
Source 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 the
popPage
Source code, which is passed inallowDebug
If the value is false, it passesreleaseUntil
Off stack current pagestop
All 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 the
releaseUntil
Realization, mainly throughTo iterate over
, to determine whether the object is equal to stop, whose purpose isBefore releasing stop
Of 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 the
kill
Implementation, primarily to destroy the current page, willThe current page is assigned to the parent page
And 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 pool
The 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 page
Pressing common objects
Mainly throughnext
Pointer to theincreasing
, - when
Page full
When, you need to set the pagechild
The object ofA new page
- When there is no pool, that is, only empty placeholders (stored in TLS), the page is created,
So, to sum up,autorelease
andobjc_autoreleasePush
The overall underlying process is shown in the following figure
- In the automatic release pool
Out of the stack
(i.e.pop
) in the operation- In the page
Offstack common objects
Mainly throughnext
Pointer to thediminishing
, - when
Empty page
When, need to assign the pageparent
The object ofThe current page
- In the page
To sum up,objc_autoreleasePoolPop
The 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 the
CFRunLoopGetMain
The 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
__CFRunLoopCreate
Source 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 the
CFRunLoopRef
The definition of theta, according to the definition, in factA RunLoop is also an object
. is__CFRunLoop
Structure 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 value
mainRunloop
,currentRunloop
thecurrentMode
- po CFRunLoopCopyCurrentMode(mainRunloop)
- po CFRunLoopCopyCurrentMode(currentRunloop)
And from there, it says,Runloop has only one mode at runtime
- To obtain
mainRunloop
Of all models, i.epo CFRunLoopCopyAllModes(mainRunloop)
It can be verified from the resultsrunloop
ε CFRunloopMode
withMore than a pair of
The 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
- in
RunLoop
View in source codeItem
Types, 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 to
The Timer for example
Generally, the timer is passed when the timer is initializedaddTimer:forMode:
Method is added to Runloop, so look it up in the source codeaddTimer
, i.eCFRunLoopAddTimer
Method, its source code implementation is as follows, its implementation is mainly judgedkCFRunLoopCommonModes
, and then find the mode of the runloop for matching processing- Among them
kCFRunLoopCommonModes
Not a pattern, but an abstractionPseudo mode
Which is more flexible than defaultMode - through
CFSetAddValue(rl->_commonModeItems, rlt);
We know that,runloop
withmode
ζ―More than a pair of
At the same time, you can getmode
δΈitem
Is alsoMore than a pair of
the
- Among them
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 onrun
Method, as you can see from the following stack information, its underlying execution is__CFRunLoopRun
methods
- Enter the
__CFRunLoopRun
Source 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
__CFRunLoopDoTimers
Source 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
__CFRunLoopDoTimer
Source code, the main logic istimer
After 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 attimer
Execution analysis, forObserver, block, source0, and source1
It 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 the
CFRunLoopRun
Source code, where the parameters passed in1.0 e10
This 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 the
CFRunLoopRunSpecific
Source 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
__CFRunLoopRun
Source 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 sources
When 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,RunLoop
The 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, because
The runloop of the child thread is not started by default
, you need torunloop run
To 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