NSRunLoop is usually used when we develop iOS apps, and NSRunLoop is actually the encapsulation of CFRunLoop in Apple’s Core Foundation framework. This time, we directly learn CFRunLoop through official documents and Core Foundation source code.

Core Foundation is a pure C version of the implementation. Apple has open-source Core Foundation source code

CFRunLoop

Declarations in header files

typedef struct __CFRunLoop * CFRunLoopRef;
Copy the code

Definition of __CFRunLoop (only the main attributes remain)

struct __CFRunLoop {
    CFMutableSetRef _commonModes;// Common mode set, which will be covered later
    CFMutableSetRef _commonModeItems;/ / the source collection
    CFRunLoopModeRef _currentMode;// Current mode in effect
    CFMutableSetRef _modes;// All modes of the current runloop
    struct _block_item* _blocks_head;/ / chain block header
    struct _block_item* _blocks_tail;// Block the end of the list
};
Copy the code

As you can see, a Run loop object doesn’t have many elements. _commonModes, _commonModeItems, _currentMode, and _modes are all related to modes. The other two attributes are a linked list that holds blocks.

So what is mode? What is the definition of mode

CFRunLoopMode

Here is the definition of CFRunLoopModeRef:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name;/ / the name of the mode
    Boolean _stopped;// The method used to exit the current mode
    // Enter the source source
    CFMutableSetRef _sources0;// Non-port-related events (e.g. user click)
    CFMutableSetRef _sources1;// Port-related events (system events)
    // Listener observer
    CFMutableArrayRef _observers;
    CFIndex _observerMask;// Listener registered listener events
    // Timer
    CFMutableArrayRef _timers;
};	
Copy the code

A mode contains three types of objects, sources(CFRunLoopSource), timers(CFRunLoopTimer), and observers(CFRunLoopObserver), upon which they must be dependent in order to accept callbacks from run loops. Their relationship to Run loop and mode is as follows:

Each thread corresponds to a Run loop object, and each run loop contains multiple modes, and each mode contains multiple sources, timers, and observers.

Each source, timer, and Observer must be added to one or more modes to take effect, but the Run loop can only run one mode at a time. If the mode currently running is not the added mode, it does not take effect.

Let’s take an example of UIScrollView

The main thread’s run loop runs NSDefaultRunLoopMode by default, and when sliding UIScrollView, the main thread’s Run loop switches to UITrackingRunLoopMode.

If you add an NSTimer to the NSDefaultRunLoopMode of the main thread, when the user slides UIScrollView, the main thread will switch the run loop to UITrackingRunLoopMode, and this NSTimer will not take effect. The timer does not kick in until the main thread switches the Run loop back to NSDefaultRunLoopMode when the user stops sliding.

The system defines a number of modes, the common ones are

  • The default mode:NSDefaultRunLoopMode(Cocoa) / kCFRunLoopDefaultMode(Core Foundation)
  • Event tracking:UITrackingRunLoopMode(Cocoa)
  • Common Modes:NSRunLoopCommonModes(Cocoa) / kCFRunLoopCommonModes(Core Foundation)

When we need to execute some higher-priority tasks, we can also customize the mode to limit some lower-priority events and ensure the execution of higher-priority tasks.

Each Mode is distinguished by name. Core Foundation does not expose the interface of Run loop Mode. Users only need to care about the Mode name. Examples are NSDefaultRunLoopMode, kCFRunLoopDefaultMode, and UITrackingRunLoopMode, all of which are strings (which can be converted via the Toll-free Bridge).

kCFRunLoopCommonModes

In the __CFRunLoop definition, the following two sentences define common modes related variables:

CFMutableSetRef _commonModes;/ / common mode set
CFMutableSetRef _commonModeItems;/ / source/timer/collection of the observer
Copy the code

Common modes are not one mode, but a collection of modes. Modes added to common modes are stored in _commonModes, and elements from _commonModeItems are added to this mode. This way _commonModeItems can be listened to by every mode runtime that _commonModes contain.

From the user’s point of view, when a timer is added to NSRunLoopCommonModes, the NSTimer takes effect regardless of which mode the run loop is currently running in, as long as the mode is in the common modes set.

Take UIScrollView above for example

In the main thread, NSDefaultRunLoopMode and UITrackingRunLoopMode are added to the common modes. This means that if we add NSTimer to the common Modes, namely kCFRunLoopCommonModes, this NSTimer will work regardless of whether the user slides UIScrollView or not.

There are two ways to deal with common modes. One is to add custom modes to them, and the other is to add sources/timers/observers to kCFRunLoopCommonModes

1. CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    // Check whether common modes already exist
    if(! CFSetContainsValue(rl->_commonModes, modeName)) {// Get the common modes set
        CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
        // Add mode to common modes set
        CFSetAddValue(rl->_commonModes, modeName);
        if (NULL! =set) {
            CFTypeRef context[2] = {rl, modeName};
            // Add each item in common Mode items to the mode passed in
            CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void*)context); }}}Copy the code

When the CFRunLoopAddCommonMode function is called, the new mode is added to the common modes set of the runloop, and all elements of the current CommonMode items are added to the new mode.

This way, whenever the mode to which the Run loop is switched is added to kCFRunLoopCommonModes, it can respond to sources/timers/observers that are added to kCFRunLoopCommonModes.

2. CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer

The kCFRunLoopCommonModes parameter can be passed when adding source/ Timer/Observer objects to the Run loop. These source/timer/ Observer will be in effect. There is only one CFRunLoopAddTimer code that relates to common modes; the other two (source/ Observer) codes are similar.

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {   
    // Check whether timer is added to kCFRunLoopCommonModes
    if (modeName == kCFRunLoopCommonModes) {
        // Get the Common Modes collection
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        // If common modes items are empty, create a collection of common modes items
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        // Add the incoming timer source to common Mode items
        CFSetAddValue(rl->_commonModeItems, rlt);
        // Add the timer to each mode in common modes
        if (NULL! =set) {
            CFTypeRef context[2] = {rl, rlt};
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void*)context); }}else {
      // Non-common mode operations}}Copy the code

Adding a timer to kCFRunLoopCommonModes adds the timer to the CommonMode items and to each mode in the common Modes collection, i.e., This function will update all modes in the Common Modes set so that the timer will take effect whenever you run in any common Mode.

CFRunLoopSource/CFRunLoopTimer/CFRunLoopObserver

We can add source/timer/ Observer objects to the Run loop to receive notification of events via callbacks. When you add a run loop, you must specify a mode. It is also possible to remove all three objects from a run loop.

CFRunLoopSourceRef

The Input source is the source of events that typically produce asynchronous events, such as messages arriving at network ports or operations performed by users. It is defined as follows:

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;//toll-free bridge
struct __CFRunLoopSource {
    CFMutableBagRef _runLoops;
    union {
		    CFRunLoopSourceContext version0;//source0
        CFRunLoopSourceContext1 version1;//source1
    } _context;
};
Copy the code

In CFRunLoopSourceRef, the two main variables CFRunLoopSourceContext and CFRunLoopSourceContext1 are declared with union, which means there are two different sources. Source0 and source1.

source0

Source0 is a generic response application event, such as a button response event. When the source0 event needs to be sent, the CFRunLoopSourceSignal function is called to mark the source as waiting to be fired, but this function cannot wake up the runloop. The CFRunLoopWakeUp method needs to wake up the corresponding Run loop. The source0 event is triggered. CFSocket in Core Foundation is implemented using source0. The definition is as follows:

typedef struct {
    CFIndex	version;//0 indicates source0
    void	(*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*perform)(void *info);
} CFRunLoopSourceContext;
Copy the code

It contains three callbacks,

  • schedule: is called once when adding source0 to run loopscheduleCallback method;
  • perform: is called when source0 is triggeredperformCallback method;
  • cancelThe cancel callback method is called when removed or run loop is destroyed.
source1

Source1 is managed by the Run loop and the kernel, and communicates using a Mach port for interprocess communication through the kernel, and can also be used for interthread communication. Source1 can wake up run loop. CFMachPort and CFMessagePort in Core Foundation are implemented using source1. The definition is as follows:

typedef struct {
    CFIndex	version;/ / 1 represents source1
    mach_port_t	(*getPort)(void *info);
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#endif
} CFRunLoopSourceContext1;
Copy the code

Among them

  • getPortThe function pointer provides a function that gets port
  • performA function is a callback function

Interestingly, CFMessagePort is often used for interprocess communication, such as iOS jailbreak development, where there is a UI application at the front for interface presentation and a Daemo Sprite at the back for task processing. CFMessagePort is no longer available on iOS7 or later, according to the official documentation. Source.

CFRunLoopSourceRefThe use of

Core Foundation provides functions that provide interaction with input sourc

  • CFRunLoopSourceGetContext: Creates the source0 or source1 variable
  • CFRunLoopSourceCreate: createCFRunLoopSourceRef
  • CFRunLoopAddSourceAdd:CFRunLoopSourceRefTo run in the loop

CFRunLoopTimerRef

CFRunLoopTimerRef is a timer for the NSTimer Toll-free Bridge. The Run loop timer is not always reliable. If the added mode is not running or the current Run loop is performing a time-consuming operation, the Run loop timer may not be triggered. In addition, the trigger time of the Run loop timer depends on the planned time interval rather than the actual time interval. For example, if a five-second repeating timer is delayed by two seconds on the second time, the third time will not change and will still be executed at the 15th second time.

A Run loop timer can be added to multiple Run loop modes at the same time, but the Run loop timer only takes effect in the first Run loop added.

CFRunLoopTimerRef contains the interval parameter and the Callback method.

The time calculation mechanism of Run loop timer will be further understood later

CFRunLoopObserverRef

CFRunLoopObserverRef is an observer that contains a callback pointer and an Activity parameter that indicates the accepted Run loop event. The event is defined as follows, and obsever is notified when the Run loop is in a different state.

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // About to enter Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // Timer is about to be processed
    kCFRunLoopBeforeSources = (1UL << 2), // Source is about to be processed
    kCFRunLoopBeforeWaiting = (1UL << 5), // About to go to sleep
    kCFRunLoopAfterWaiting  = (1UL << 6), // Just woke up from hibernation
    kCFRunLoopExit          = (1UL << 7), // About to exit Loop
};
Copy the code

blocks

In the __CFRunLoop definition, there are two more definitions of block lists

struct _block_item* _blocks_head;
struct _block_item* _blocks_tail;
Copy the code

Run loop also provides a CFRunLoopPerformBlock function for adding blocks. Similar to source0, this method does not wake up the Run loop. CFRunLoopWakeUp is called to wake up the Run loop.

CFRunLoopPerformBlock is defined as follows

void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))
Copy the code

Each time the Run loop runs, it checks several times to see if there are currently any blocks that need to be executed, and if there are, the incoming blocks are executed.

CFRunLoopGet

In normal development, we usually don’t care about the run loop lifecycle. The system will automatically create a Run loop object for us in the main thread. The main thread’s Run loop object can be obtained through the following interface, which returns an instance of CFRunLoopRef.

CFRunLoopRef CFRunLoopGetMain(void);
Copy the code

When we create a new thread, the run loop object is not initialized by default. We need to call a function to get the run loop object of the current thread.

CFRunLoopRef CFRunLoopGetCurrent(void);
Copy the code

So the function that creates the Run loop object is implemented internally in Core Foundation, Both CFRunLoopGetMain and CFRunLoopGetCurrent internally call the same method _CFRunLoopGet0(pthread_t t).

The concept of THREAD Specific Data (TSD) is briefly mentioned here. In a multi-threaded environment, because the data space is shared, global variables are also shared by all threads. So when you need data that is only accessible in the current thread, you use TSD to store it. TSD stores data that is only valid in the current thread, but can be accessed across functions.

The Run Loop object is stored in TSD.

CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //__CFRunLoops is a dict that stores runloop objects globally and initializes the dict on first run
    if(! __CFRunLoops) { __CFUnlock(&loopsLock);// Create a global DITC and add a runloop object for the main thread
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
        // Create a run loop object for the main thread using the __CFRunLoopCreate method
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        // Swap dict with __CFRunLoops, then release ditc
        if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile*)&__CFRunLoops)) { CFRelease(dict); }}// Get the corresponding runloop from the thread object
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    // Initialize the thread's Runloop object if it has not already created one
    if(! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t);if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
    }
    // Determine whether to get the runloop object of the current thread
    if (pthread_equal(t, pthread_self())) {
        // Store the run loop object in TSD
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            // Set the destruction callback here to destroy the Runloop object at the end of the thread life cycle
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void*))__CFFinalizeRunLoop); }}return loop;
}
Copy the code

The flow of this function is as follows

  1. On first entry, a global dict is created to store the run loop object for each thread, and the run loop object for the main thread is initialized.
  2. Gets the corresponding Run loop object based on the thread passed in. If it is null, a run loop object is created and added to the global dict. The run loop is also placed in the TSD of the corresponding thread and a thread-end destruction callback is set.

Using the above function, we can get the run loop object of the current thread. When you add the source/timer/observers functions mentioned earlier, you need to pass in a Run loop object.

The system will start the run loop of the main thread. For other threads, we need to take the initiative to start after obtaining the Run loop object.

CFRunLoopRun

First, take a look at the main logic of CFRunLoopRun.

PS: Source0 (port) on the left should be source1(port)

Core Foundation provides two functions to start the run loop, CFRunLoopRun and CFRunLoopRunInMode. The functions are declared as follows:

// Start run loop in default mode with no arguments
void CFRunLoopRun(void);
// Set the mode for the run loop to run, the validity period, and whether to exit directly after the source 0 event
SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
Copy the code

Both functions call the CFRunLoopRunSpecific function, which is implemented as follows, with the numbers in the comments corresponding to the order in the diagram

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    // Check whether mode has source,timer, or block events. If not, run loop exits immediately
    if (__CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        return kCFRunLoopRunFinished;
    }
    int32_t retVal = kCFRunLoopRunFinished;
    //1. Run loop is about to enter, notifying Observers
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
	  do {
        //2. Notify the observer that the timer is about to trigger
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //3. Notify the observer that the source0 (non-port based) input source is about to be fired
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        / / processing blocks
        __CFRunLoopDoBlocks(rl, rlm);
        //4. Handle source0, which is the non-port based input source
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
          	// process blocks after processing source0
            __CFRunLoopDoBlocks(rl, rlm);
        }
        Check if there are any source1 events that need to be handled, usually system-level events. Passing 0 to the third from last indicates an immediate return
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // jump to handle_msg to handle the source1 event directly
            goto handle_msg;
        }
        
        // Go to sleep without source1
        //6. Notify the observer that Runloop is going to sleep
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        Call mach_msg and switch to kernel mode to receive message
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        // Exit the sleep state when there is source1, timer or manual wake up
        //8. Notify the observer that Runloop exits the wait
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:;
        //9. There are events to take care of when you are out of sleep
        // The timer needs to trigger
        if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// Trigger the timer
            if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Calculate the next trigger time__CFArmNextTimerInMode(rlm, rl); }}else if (livePort == dispatchPort) {
            // If a block is dispatched to main_queue, execute the block.
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {/ / source1 event
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
        // Execute the block callback again
        __CFRunLoopDoBlocks(rl, rlm);
        
        // Determine whether to exit the run loop
        if (sourceHandledThisLoop && stopAfterHandle) {
            // Start run loop with source0 and exit immediately
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            // Set timeout when start run loop
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            // Run loop is set to stop
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            // Run loop mode is set to stop
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // The current mode has been removedretVal = kCFRunLoopRunFinished; }}while (0 == retVal);
    //10. Run loop has exited, notify observers
	  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  
    return retVal;
}
Copy the code

You can see that in the code

  • Run loop through do… While loop implementation, as long as the exit condition is not met, the run loop will sleep or run.
  • The run loop must add a source, timer, or block event to run, otherwise it will exit directly
  • Run loop is called when the run loop enters hibernationmach_msgThe function switches to kernel state (when we hit pause while the app is running, we can see that the call stack stays atmach_msg_trap()This method)

PerformSelecter

When calling NSObject performSelecter: afterDelay: after its internal will actually create a Timer and add to the current thread RunLoop. So if the current thread does not have a RunLoop, this method will fail.

When the performSelector: onThread: when, in fact, it will create a source0 added to the corresponding thread to, in the same way, if the corresponding thread no RunLoop this method will fail.

Apple’s application of RunLoop

With the RunLoop implementation in mind, let’s take a look at how Apple uses RunLoop. This section focuses on an in-depth understanding of RunLoop, which is only partially expanded here.

Add a breakpoint to the viewDidLoad method and print [NSRunLoop currentRunLoop] to print some details of the runLoop object for the current thread

<CFRunLoop 
current mode = kCFRunLoopDefaultMode,
common modes = {
	 "UITrackingRunLoopMode"
	 "kCFRunLoopDefaultMode"
}

common mode items = {
  //source 0
	CFRunLoopSource { 
  	order = -1, version = 0, callout = PurpleEventSignalCallback 
  }
	CFRunLoopSource { 
    order = -1, version = 0, callout = __handleEventQueue 
  }
	CFRunLoopSource { 
    order = 0, version = 0, callout = FBSSerialQueueRunLoopSourceHandler 
  }
	CFRunLoopSource {
    order = -2, version = 0, callout = __handleHIDEventFetcherDrain
  }

  //source 1
	CFRunLoopSource {
    order = 0, port = 43275
  }
	CFRunLoopSource {
    order = -1, version = 1, callout = PurpleEventCallback
  }
	CFRunLoopSource {
    order = 0, port = 42755
  }

  //UI drawing is relevant
	CFRunLoopObserver {
    activities = 0xa0, order = 1999000, callout = _beforeCACommitHandler
  }
	CFRunLoopObserver {
    activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
  }
	CFRunLoopObserver {
    activities = 0xa0, order = 2001000, callout = _afterCACommitHandler
  }

  // Auto Release pool
	CFRunLoopObserver {
    activities = 0x1, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
  }
	CFRunLoopObserver {
    activities = 0xa0, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
  }

	CFRunLoopObserver {
    activities = 0x20, order = 0, callout =_UIGestureRecognizerUpdateObserver
  }
}

modes = {
	// Modes included with RunLoop and modes in observers/sources/timers
}
Copy the code

AutoreleasePool

Apple in the main thread RunLoop CommonModes registered two Observer, which is _wrapRunLoopWithAutoreleasePoolHandler callback function.

<CFRunLoopObserver>{
  order = -2147483647, activities = 0x1, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
<CFRunLoopObserver>{
  order = 2147483647, activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
Copy the code

The first Observer sets the highest priority -2147483647, which is triggered when entering the RunLoop (0x1).

The second Observer sets the lowest priority, 2147483647, to be triggered when RunLoop goes to sleep or exits (0xa0).

_wrapRunLoopWithAutoreleasePoolHandler

By adding the — in the XCode Breakpoint add breakpoints, see _wrapRunLoopWithAutoreleasePoolHandler corresponding assembly code.

NSPushAutoreleasePool and NSPopAutoreleasePool are two places in the code to jump to other functions. One is the creation of autlreleasePool. The other is the release of autlreleasePool, which presumably determines the current state of the RunLoop and then executes a different function.

Then we continue to add breakpoints to NSPushAutoreleasePool and NSPopAutoreleasePool, eventually calling objc_autoreleasePoolPush and objc_autoreleasePoolPop, respectively.

The first Observer creates the autoreleasePool as it enters the RunLoop, and its order=-2147483647 ensures that it is called before all callbacks.

The second Observer releases the autoreleasePool and then creates the autoreleasePool while RunLoop is asleep; When RunLoop exits, release autoreleasePool, whose order=2147483647 guarantees that it is called after all callbacks.

conclusion

This paper mainly studied the source code of RunLoop in the Core Foundation framework, understood the implementation principle of RunLoop, and further explored and studied the application of RunLoop in the future.