directory

1. Introduction and functions of Runloop

2. The RunLoop open

3.RunLoop object and acquisition

4. The underlying structure of RunLoop

5. Relationship between RunLoop and thread

6.RunLoop overall processing logic

7. The application of the RunLoop


Apple has made the CoreFoundation source code open source, and the CFRunloop source code. We’ll explore runloop based on the source code later. The official documentation

1. Introduction and functions of Runloop

  • Introduction: Runloop is a “dead” cycle, the program starts, the response of the Runloop will continue to deal with the user action (e.g., click on the screen, swiping the screen), timer, system events, etc., not finished when all events, would be the rest wait state, once the incident will be awakened to handle events, to ensure the normal operation of the program.

  • Basic functions:

1.Keep the program runningWhen the program is started, a main thread is opened, and a RunLoop corresponding to the main thread is run. The RunLoop ensures that the main thread will not be destroyed, which ensures that the program will continue to run.Handle various events for the APP(for example: touch event, slide event, timer event, block, etc.) 3.Save CPU resources and improve program performanceWhen the program is running, RunLoop notifies the CPU that nothing is being done and that it has gone to sleep, at which point the CPU frees up its resources to do something else. When something is being done, RunLoop immediately wakes up to do something else.

Let’s take a quick look at this image from apple’s official documentation:

2. The RunLoop open

  • UIApplicationMain starts Runloop. The application does not exit immediately, but remains running. Therefore, every application must have a runloop. We know that when the main thread starts, it will run a runloop corresponding to the main thread. Therefore, the runloop must be opened in the main function of the application.
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}/ / into the UIApplicationMain
UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);

// We find that it returns an int, so let's make some changes to main
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"Start");
        int re = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        NSLog(@"The end");
        returnre; }}Conclusion / * : UIApplicationMain does not return a RunLoop related to the main thread. UIApplicationMain does not return a RunLoop related to the main thread. * /
Copy the code

Let’s look at the source code for Runloop

/** Open RunLopp entry RunLoop is a do while loop */
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        // argument: the current thread loop is executed with DefaultMode as the first mode
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false); / / / / core
        CHECK_FOR_FORK(a); }while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code

We see that RunLoop is indeed implemented by the do while loop by judging the value of result. Therefore, we can think of RunLoop as an infinite loop. If there is no RunLoop, UIApplicationMain will return directly after execution, and the program will exit.

3.RunLoop object and acquisition

  • Fundation framework (wrapper based on CFRunLoopRef) NSRunLoop object

  • CoreFoundation CFRunLoopRef object

Because Fundation framework is based on a layer of OC encapsulation of CFRunLoopRef, here we mainly study the source code of CFRunLoopRef

//Foundation
[NSRunLoop currentRunLoop]; // Get the RunLoop object for the current thread
[NSRunLoop mainRunLoop]; // Get the main thread RunLoop object

//Core Foundation
CFRunLoopGetCurrent(a);// Get the RunLoop object for the current thread
CFRunLoopGetMain(a);// Get the main thread RunLoop object

Copy the code

4. The underlying structure of RunLoop

CFRunLoopRef Runloop (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;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
Copy the code

The Runloop structure contains a lot of parameter information. We mainly look at CFRunLoopModeRef and CFMutableSetRef variables

CFRunLoopModeRef _currentMode; // The current mode
CFMutableSetRef _modes; // The RunLoopMode set contains many modes
Copy the code

CFRunLoopModeRef is a pointer to the __CFRunLoopMode structure. The __CFRunLoopMode structure source code is as follows:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    
    CFMutableSetRef _sources0; 
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
Copy the code

There are mainly the following four member variables:

CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
Copy the code

From the above analysis, we have a general understanding of the internal structure of RunLoop. Next, we will analyze the related classes of RunLoop in detail. The following are the Core Foundation classes for RunLoop:

CFRunLoopRef – Gets the current RunLoop and the primary RunLoop

Cfrunloopmoderef-runloop Indicates the running mode. Only one mode can be selected and different operations can be performed in different modes

CFRunLoopSourceRef – Event source, input source CFRunLoopTimerRef – Timer time CFRunLoopObserverRef – Observer

The diagram is as follows:

CFRunLoopModeRef (model), CFRunLoopSourceRef (source0, source1), CFRunLoopTimerRef (timer), Observer (CFRunLoopObserverRef) The role of these elements.

4.1 CFRunLoopModeRef:

CFRunLoopModeRef represents the running mode of Runloop.

  • A Runloop contains several modes, and each Mode in turn contains several sources, timers, and observers
  • Each time RunLoop starts, only one of the modes can be specified, which is called CurrentMode
  • If you need to switch Mode, you can only exit the RunLoop and re-specify a Mode to enter. This is mainly to separate the Source, Timer, and Observer groups from each other so that they do not affect each other. If there is no any Source0 / Source1 / Timer Mode/Observer, RunLoop immediately exit.

Mode types: The system registers five modes by default. The most common ones are 1 and 5

  1. NSDefaultRunLoopMode: the default Mode of the App, in which the main thread usually runs
  2. UITrackingRunLoopMode: interface tracing Mode, used by ScrollView to track touch sliding, ensuring that the interface sliding is not affected by other modes
  3. NSConnectionReplyMode: used by the system to monitor replies, not used by development.
  4. NSModalPanelRunLoopMode: This mode is used by the system to identify events for the modal panel and is not used by development.
  5. NSRunLoopCommonMode: this is a placeholder Mode used to mark NSDefaultRunLoopMode and UITrackingRunLoopMode, not a real Mode

Switch between Mode, look at the source code:

RunLoop is a do while loop */
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        // argument: the current thread loop is executed with DefaultMode/as the first mode
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false); / / / / core
        CHECK_FOR_FORK(a); }while (kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result);
}


// How to switch mode
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK(a);If the runloop is released, return to Finish
    if (__CFRunLoopIsDeallocating(rl))
        return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    // Find mode by modeName and assign it
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    if (NULL = = currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false; //Empty mode has no resources ()
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        
        If Empty Mode has no resources, return Finish
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    
    // 1 Save previousMode
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    /* currentMode= defalutMode rl->_currentMode = defalutMode previousMode = defalutMode rl->_currentMode = trackingMode */
    //previousMode Saves the currently passed mode
    CFRunLoopModeRef previousMode = rl->_currentMode;//
    
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    //========== handle mode-related stuff =========//
	if (currentMode->_observerMask & kCFRunLoopEntry )
        // Tell the system the status of Runloop is entered kCFRunLoopEntry
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
     // Core methods handle things in mode
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

	if (currentMode->_observerMask & kCFRunLoopExit )
        // Tell the system that the status of Runloop is kCFRunLoopExit
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    / / = = = = = = = = = = = = = = = = = = = = = = = = = = / /
    
    // Switch to the previous model
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    // rl->_currentMode = defalutMode Restore the previous mode
	rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}
Copy the code

The summary is as follows:

1. Enable runloop. By default, the first mode is defaultMode and currentMode is defaultMode 2. When we swipe, we save the previousMode with previousMode, so previousMode is defaultMode, and currentMode is switched to trackingMode 3. When we stop sliding, we’ll go back to our original Mode, currentMode = previousMode, so currentMode is defaultMode again

The defaultMode timer is tackingMode and the defaultMode timer is tackingMode. The defaultMode timer is tackingMode. Stopping the slide timer works again (defaultMode is restored again).

Here is a code to look at the usual use of NSTimer problems:

- (void)viewDidLoad {
   [super viewDidLoad];
   
   NSTimer * timer  = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
  // Add the timer to the RunLoop and select the default running mode NSDefaultRunLoopMode = kCFRunLoopDefaultMode
   [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
   
  // When we add timer to the UITrackingRunLoopMode mode, the timer will only run if we slide textField
   // [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
   
   UIScrollView *scrollView = [[UIScrollView alloc] init];
   scrollView.backgroundColor = [UIColor redColor];
   scrollView.frame = CGRectMake(10.100.300.300);
   scrollView.contentSize = CGSizeMake(300.800);
   scrollView.delegate = self;
   [self.view addSubview:scrollView];

}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
   NSLog(@"scrollViewDidEndDecelerating");
}


-(void)timerAction{
   
   NSLog(@"timerAction");
}

Copy the code

Through printing, it can be found that when we drag scrollView all the time, the timer stops running, and then stops dragging scrollView, the timer starts running again, thus confirming the above conclusion.

How to solve:

    /* All NSRunLoopCommonModes marked by NSRunLoopCommonModes will work, so we just set Mode to NSRunLoopCommonModes. Timer can run in UITrackingRunLoopMode and kCFRunLoopDefaultMode
   [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code

4.2 Source0 with Source1

  • Source1: mach_port-based event from the kernel or another process or thread that actively wakes up a dormant RunLoop (typically controlled by the system during development of interprocess communication in iOS). Just think of mach_port as a mechanism for sending messages between processes.

  • Source0: Non-port-based processing events for user active triggers, such as button clicks, screen clicks, and PerformSelectors

Let’s write a click screen method and PerformSelectors to see what’s in it:

As you can see from the breakpoint print above, touch events and PerformSelectors trigger Source0 events.

  • Events trigger the overall flow:

When we trigger an event (touch/lock screen/shake, etc.), IOKit. Framework generates an IOHIDEvent event. IOKit is a hardware driven framework of Apple. IOHIDServices and IOHIDDisplays are two components. IOHIDServices deals with user interactions by encapsulating events into IOHIDEvents objects, which are then forwarded to the required App processes using Mach ports. Then Source1 will receive IOHIDEvent, then the callback __IOHIDEventSystemClientQueueCallback (), __IOHIDEventSystemClientQueueCallback trigger Source0 within (), Source0 trigger _UIApplicationHandleEventQueue again (). So the touch event is seen inside Source0.

4.3 Timers: NSTimer

It is obvious from the print that the timer is a RunloopDoTimer event

4.4 the Observer:

Observer: a listener that monitors the status of the RunLoop. The status of the RunLoop can be handled according to service requirements. Look directly at the code:

/* create listener CFRunLoopObserverCreate(<#CFAllocatorRef allocator#>, <#CFOptionFlags activities#>, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext *context#>) CFAllocatorGetDefault() assigns the second parameter CFOptionFlags Activities by default: > > < span class = > cfrunloopAllActivities > < span class = > < span class = > CFIndex order > < span class = > Call back two arguments observer: listener activity: listener event */

/* Observer typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0), // Approaching RunLoop kCFRunLoopBeforeTimers = (1UL << 1), // Approaching Timer kCFRunLoopBeforeSources = (1UL << 2), Source kCFRunLoopBeforeWaiting = (1UL << 5), KCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopExit = (1UL << 7), and RunLoop is about to exit kCFRunLoopAllActivities = 0x0FFFFFFFU }; * /

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
      switch (activity) {
          case kCFRunLoopEntry:
              NSLog(@"KCFRunLoopEntry: RunLoop enter");
              break;
          case kCFRunLoopBeforeTimers:
              NSLog(@"KCFRunLoopBeforeTimers: RunLoop is about to handle Timers");
              break;
          case kCFRunLoopBeforeSources:
              NSLog(@"KCFRunLoopBeforeSources: RunLoop is going to handle Sources");
              break;
          case kCFRunLoopBeforeWaiting:
              NSLog(@"KCFRunLoopBeforeWaiting: RunLoop is going to sleep");
              break;
          case kCFRunLoopAfterWaiting:
              NSLog(@"KCFRunLoopAfterWaiting: RunLoop wakes up");
              break;
          case kCFRunLoopExit:
              NSLog(@"KCFRunLoopExit: RunLoop exits");
              break;

          default:
              break; }});// Add listeners to RunLoop
/* CFRunLoopRef rl: The second parameter, CFRunLoopObserverRef Observer, and the third parameter, CFStringRef mode, listen for the status of the RunLoop in which mode */
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

// Enable runloop for child threads
[[NSRunLoop currentRunLoop] run];


/* Release */ is the last release for Core Foundation functions that contain the words Create, Copy, Retain, etc
CFRelease(observer);

Copy the code

5. Relationship between RunLoop and thread

Let’s take a look at the main points between runloops and threads:

  • Each thread has a unique RunLoop object corresponding to it
  • Runloops are stored in a global Dictionary, with threads as keys and runloops as values
  • The RunLoop for the main thread is created automatically, and the RunLoop for the child thread needs to be created actively
  • The RunLoop is created on the first fetch and destroyed at the end of the thread

Verify this with the source code:

RunLoop is a do while loop */
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        // Call CFRunLoopGetCurrent() when runloop starts
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false); 
        CHECK_FOR_FORK(a); }while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }// Get the current Runloop call _CFRunLoopGet0
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK(a); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

// The incoming thread returns the runloop corresponding to the thread
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        // t==0 indicates the master thread
        t = pthread_main_thread_np(a); } __CFLock(&loopsLock);if(! __CFRunLoops) { __CFUnlock(&loopsLock);// It is obvious that a dictionary is created
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
        // Get the main thread corresponding RunLoop based on the main thread passed in
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // Save the main-key and runloop-value to the dictionary
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // Get runloops with a dictionary __CFRunLoops, key: thread
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    
    // If the loop is empty, a new loop will be created
    if(! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if(! loop) {// Create a dictionary with a thread as the key and runloop as the value, one to one in the dictionary, and return the dictionary runloop directly on the next fetch
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&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

The open RunLoop method is called from the current thread and the corresponding RunLoop is called from the main thread. If the current thread has no runloop, a runloop 3 will be created automatically. The thread is the key, runloop is the value, one to one is stored in the dictionary, and the next fetch is directly fetched in the dictionary, which fully demonstrates why the thread and runloop are one-to-one correspondence.

6.RunLoop overall processing logic

First analyze the source code:

// Simplify the CFRunLoopRunSpecific function code, which internally calls the __CFRunLoopRun function
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // notify Observers: enter Loop
    // __CFRunLoopDoObservers is internally called __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__functionif (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // Core Loop logic
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // notify Observers: exit Loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

// The __CFRunLoopRun function is stripped down, leaving the main code intact
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // Notify Observers that Timers are about to be processed
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
        
        // Notify Observers that Sources is about to be handled
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        / / processing Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        / / Sources0 processing
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            / / processing Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // If there is Sources1, jump to the handle_msg tag
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        
        // Notify Observers that they are about to hibernate
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        // Go to sleep and wait for another message to wake up
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        do {
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        
        Wake up / /
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        
        // Notify Observers: Observers have been astounded
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg: // See who woke up the RunLoop and handle it accordingly
        if(awakened by Timer) {/ / handle the Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }
        else if{__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); }else { // Awakened by Sources1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
        
        / / execution Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        RetVal = retVal; retVal = retVal
        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; }}while (0 == retVal);
    
    return retVal;
}
Copy the code

Also in order to facilitate understanding, we use the figure according to the source code:

7. The application of the RunLoop

1. Thread survival: See the next article on iOS resident threads

The first observer listens for the event kCFRunLoopEntry. The callback uses _objc_autoreleasePoolPush() to create the automatic release pool, which has the highest priority and is ensured to precede all other callbacks. The second observer listens for two events:

  • Before Waiting calls _objc_autoreleasePoolPop and push to empty the automatic release pool.
  • Call _objc_autoreleasePoolPop on Exit torelease the empty.

The previous article also had a detailed explanation: a summary of the principles of autoreleasePool

3. Monitor Caton.

4. Performance optimization: refresh scheme for mass data display (such as live broadcast rounds).


If there are flaws in the article, welcome to point out, thank you ~