What is a RunLoop

1. The RunLoop concept

A RunLoop is a loop that processes time while a program is running. Each thread has a RunLoop corresponding to it. When a RunLoop has an event to process, it processes the event. When there are no events, the thread goes to sleep.

In iOS, there are NSRunLoop and CFRunLoopRef, and the former is an encapsulation of the latter.

2. Functions of RunLoop

RunLoop handles timers, GCD, event responses, UI refreshes, network requests, and AutoReleasePool

3. Source code definitions related to RunLoop

In Core Foundation, there are five major classes associated with RunLoop

  • CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;

struct __CFRunLoop {
    pthread_mutex_t _lock;	// Handle locks used by modes
    __CFPort _wakeUpPort;	// Wake up the RunLoop Port
    pthread_t _pthread;    // Thread corresponding to RunLoop
    CFMutableSetRef _commonModes; // A collection of modes in commonModes mode
    CFMutableSetRef _commonModeItems;   // A collection of CommonDModeItems
    CFRunLoopModeRef _currentMode; // The current mode
    CFMutableSetRef _modes;  // All modes

    // ...
};
Copy the code
  • CFRunLoopModeRef
  1. RunLoop can only run in one Mode when running. If you need to change Mode, you can only stop the current Mode and restart it with a new Mode.
  2. Mode stores the current sources0, sources1, observer, timer, etc.
  3. If there are no sources0 and so on in the current RunLoop, RunLoop exits immediately
  4. Mode: KCFRunLoopDefaultMode (App’s default Mode, RunLoop usually runs in this Mode), UITrackingRunLoopMode(for interface tracking, when ScrollView slides to ensure that the slide is not affected by other modes)
typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;
    CFMutableSetRef _sources0; 
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};
Copy the code
  • CFRunLoopSourceRef
  1. Source0: contains touch events, performSelector events, etc
  2. Source1: Mainly contains port-based thread communication and system event capture
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;	/* Source0 */
        CFRunLoopSourceContext1 version1;	/* Source1 */
    } _context;
};

Copy the code
  • CFRunLoopTimerRef
  1. NSTimer
  2. PerformSelector delays execution events
typedef struct  __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;  // Start time
    CFTimeInterval _interval;	// Time interval
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};
Copy the code
  • CFRunLoopObserverRef
  1. Listen for notifications of RunLoop status
  2. Notification of UI refresh (BeforeWaiting)
  3. Autorelease Pool notification (BeforeWaiting)
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;	/ / the Observer state
    CFIndex _order;			/* immutable */
    CFRunLoopObserverCallBack _callout;	/* immutable */
    CFRunLoopObserverContext _context;	/* immutable, except invalidation */
};

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   / / to enter
    kCFRunLoopBeforeTimers = (1UL << 1),    // Before processing the timer
    kCFRunLoopBeforeSources = (1UL << 2),   // Before processing the source
    kCFRunLoopBeforeWaiting = (1UL << 5),   / / before sleep
    kCFRunLoopAfterWaiting = (1UL << 6),    / / after dormancy
    kCFRunLoopExit = (1UL << 7),    / / before exit
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

4. Obtain the current RunLoop

  • CFRunLoopGetCurrent

The current RunLoop can be obtained by CFRunLoopGetCurrent

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK(a); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
Copy the code
  • _CFRunLoopGet0

If it cannot be found directly, this method lookup is called, and the argument is the current thread. You can prove that the RunLoop is thread-bound

static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        // If the passed thread is empty, the main thread defaults
        t = pthread_main_thread_np(a); } __CFLock(&loopsLock);// If it is the first time to enter, you need to create a global dictionary
    if(! __CFRunLoops) { __CFUnlock(&loopsLock);// Create a dictionary
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);

        // Create RunLoop for the main thread
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());

        // Add the main thread and RunLoop 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 the RunLoop for the incoming thread from the global dictionary
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if(! loop) {// If not in the global dictionary, then pass in the current thread T to create a new RunLoop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if(! loop) {// Put the RunLoop and thread into the global dictionary
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        __CFUnlock(&loopsLock);
        // Assign newLoop to loop and release it
        CFRelease(newLoop);
    }

    return loop;
}
Copy the code

RunLoop Running process

Text description

  1. Notify the Observer that the Loop is entered
  2. Notifies the Observer that the Timer is about to be processed
  3. Notify the Observer that the Source is about to be processed
  4. Processing Blocks
  5. Process Source0 and possibly process Blocks again
  6. If Source1 exists, go to Step 8
  7. Notify the Observer: start hibernation and wait to be awakened
  8. Notify the Observer: End sleep and process the response
    1. To deal with the Timer
    2. Process GCD messages
    3. Deal with Source1
  9. Processing Blocks
  10. Based on the previous execution results
    1. Go back to Step 2 to continue the Loop
    2. Out of the Loop
  11. Notify the Observer that the Loop exits

Photo version (photo from the Internet)

Source validation

  1. CFRunLoopRun

CFRunLoopRun essentially starts a do-while loop that will continue if the result is not Stop or Finish

void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
    } while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code
  1. CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    // If the current runloop is released, return Finish
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl); / / lock runloop

    // Get currentMode where runloop enters (already locked)
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

    // If there is no mode, or there is no source/timer/observer in mode, unlock and return
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }

    // Change and record the current mode of runloop
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;

    // Initialize result
    int32_t result = kCFRunLoopRunFinished;
    
    // If the current mode is about to enter, inform Observers :Entry (Step 1)
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    // Perform the inner function of runloop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    // If the current mode is about to Exit, inform Observers :Exit (Step 11)
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    / / unlock mode
    __CFRunLoopModeUnlock(currentMode);
    // Restore the previous mode
    rl->_currentMode = previousMode;
    / / unlock runloop
    __CFRunLoopUnlock(rl);
    return result;
}
Copy the code
  1. __CFRunLoopRun internal function
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    // Return Stop
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    
    // Initialize the return value
    int32_t retVal = 0;
    do {
        // Set runloop to cancel wake up ignore (can receive messages)
        __CFRunLoopUnsetIgnoreWakeUps(rl);

        // If mode is Timer, notify the Observer that the Timer is about to be processed (Step 2)
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

        // If mode is Sources, inform the Observer that Sources are to be processed (Step 3)
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // Runloop processing Blocks (Step 4)
        __CFRunLoopDoBlocks(rl, rlm);
        
        // Runloop processes Source0 and may process Blocks again after processing (Step 5)
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) { msg = (mach_msg_header_t *)msg_buffer;
            // If there is a Source1 based on MachPort, jump to handle_msg for processing (Step 6)
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                gotohandle_msg; }}// Notify the Observer that sleep is about to start (Step 7)
        if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl);// Go into hibernation and wait to be awakened
        
        // You can unlock runloop and mode while sleeping
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
        // Record the start time of the sleep, used to calculate how long the sleep
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent(a);do {
            msg = (mach_msg_header_t *)msg_buffer;
            // Awaken by MachPort
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) { 
                    // The timer times out to wake up
                    rlm->_timerFired = false;
                    break;
                } else {
                    if(msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg); }}else {
                break; }}while (1);

        
        
        // Re-lock runloop and mode when awakened
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        
        // Calculate the sleep time of runloop
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
        
        // Set runloop to ignore wake up (no longer receiving messages)
        __CFRunLoopSetIgnoreWakeUps(rl);

        // Notify the Observer that runloop is awake (Step 8)
        __CFRunLoopUnsetSleeping(rl);
        if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);// Process the awakened message
    handle_msg:;

        // Set runloop to ignore wake up (no longer receiving messages)
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING(a);// handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP(a);// do nothing on Mac OS
        }
        else if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) {CFRUNLOOP_WAKEUP_FOR_TIMER(a);/ / handle the timer
            if(! __CFRunLoopDoTimers(rl, rlm,mach_absolute_time())) {
                // Re-arm the next timer__CFArmNextTimerInMode(rlm, rl); }}else if (livePort == dispatchPort) {
            / / handle the GCD
            CFRUNLOOP_WAKEUP_FOR_DISPATCH(a); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            / / processing Source1
            CFRUNLOOP_WAKEUP_FOR_SOURCE(a);voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
            
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                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); }}}/ / processing Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        // Set retVal based on the execution result
        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