A RunLoop, as the name suggests, is a loop that does something while the program is running.

Application scope:

1. Timer, PerformSelector 2, GCD Async Main Queue 3. Event response, gesture recognition, interface refresh 4Copy the code

Runloop is used in all of these.

The basic functions of RunLoop are as follows: 1. Keep the application running continuously 2. Handle various events in the App (such as touch events, timer events, etc.) 3. Save CPU resources, improve program performance: work when you should work, rest when you should rest

RunLoop object

Ios has two sets of apis to access and use RunLoop
  • Foundation:NSRunLoop
NSRunLoop *runloop1 = [NSRunLoop currentRunLoop]; NSRunLoop *runloop2 = [NSRunLoop mainRunLoop];Copy the code
  • Core Foundation:CFRunLoopRef
Runloop CFRunLoopRef runloop3 = CFRunLoopGetCurrent(); Runloop CFRunLoopRef runloop4 = CFRunLoopGetMain();Copy the code
/ / print the above four object memory address NSLog (@ "% % % % p p p p", runloop1, runloop2, runloop3, runloop4);Copy the code

We were surprised to find that the main thread runloop obtained by the two apis was not an object. This is because the NSRunLoop object wraps a CFRunLoopRef object, which is essentially a CFRunLoopRef object.

CFRunLoopRef

Five classes for RunLoop in Core Foundation

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
Copy the code
typedef struct __CFRunLoop * CFRunLoopRef; struct __CFRunLoop { CFRuntimeBase _base; pthread_t _pthread; // The corresponding thread CFMutableSetRef _commonModes; //Set<CFStringRef> CFMutableSetRef _commonModeItems; //Set<Source/Observer/Timer> CFRunLoopModeRef _currentMode; // The current CFRunLoopModeRef object CFMutableSetRef _modes; // Store a bunch of CFRunLoopModeRef objects};Copy the code
typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode { CFStringRef _name; CFMutableSetRef _sources0; // Store the CFRunLoopSourceRef object CFMutableSetRef _sources1; // Store CFRunLoopSourceRef CFMutableArrayRef _observers; // Store CFRunLoopObserverRef object CFMutableArrayRef _timers; // Store CFRunLoopTimerRef object};Copy the code

Their relationship is shown below

  • CFRunLoopModeOn behalf ofRunLoopOperation mode of
  • aRunLoopContains severalMode, each Mode contains severalSource0/Source1/Timer/Observer.
  • RunLoopYou can select only one of them when starting upModeAs acurrentMode.
  • If you want to switchMode, can only exit currentLoopAnd choose another oneModeTo enter.
  • ifModeI don’t have anySource0/Source1/Timer/Observer.RunLoopWill quit right away.

Note: under the different mode of Source0 / Source1 / Timer/Observer separated, each other, improve efficiency

CFRunLoopModeRef

There are the following modes

KCFRunLoopDefaultMode The default Mode in which the main thread normally runs the UITrackingRunLoopMode trace Mode, KCFRunLoopCommonModes placeholder Mode, not a real Mode, just a flag. The equivalent of kCFRunLoopDefaultMode + UITrackingRunLoopMode UIInitializationRunLoopMode App start after excessive Mode, Start to finish is not use GSEventReceiveRunLoopMode Graphic Mode related events, usually in less thanCopy the code

The following two basic don’t we use can be ignored in each Mode has a store this several Source0 / Source1 / Timer/Observer

  • Source0
    • Touch event handling
    • performSelector:onThread:
  • Source1
    • Port-based communication between threads
    • System Event Capture
  • Timers
    • NSTimer
    • performSelector:withObject:afterDelay:
  • Observers
    • Used to listen for the status of RunLoop
    • UI refresh (BeforeWaiting)
    • AutoRelease pool (BeforWaiting)

We see the call stack on the left going straight from 15 to 1, which we enter at the debug consolebt, showing all call stacksThis is also demonstrated in other cases of Source0, Timers, and Observers.

CFRunLoopRunSpecific, __CFRunLoopRun, __CFRunLoopDoSources0, __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_F were also called UNCTION__ method. CFRunLoopRunSpecific and __CFRunLoopRun are the theme functions of runloop. __CFRunLoopDoSources0 and __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ are Source0 handlers, The same applies to the Source1/Timer/Observer handler.

CFRunLoopObserverRef

We can add an observer to the runloop to listen for state changes in the runloop. Runloop has the following states

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), KCFRunLoopBeforeTimers = (1UL << 1), // Timer kCFRunLoopBeforeSources = (1UL << 2), // Source kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), KCFRunLoopExit = (1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU // All states};Copy the code

Add a listener to runloop

/ / create listening object / / kCFRunLoopAllActivities to monitor all the objects / / YES repeat listening / / 0 NSIntegerMax priority: For priority min / / / / Null MYRunLoopObserverCallBack callback here for callback function is introduced to some value in the callback function info get CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, MYRunLoopObserverCallBack, NULL); // Add listener CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); // Release CFRelease(observer); / / to monitor function void MYRunLoopObserverCallBack (CFRunLoopObserverRef observer, CFRunLoopActivity activity, void * info) {}Copy the code

There is another way to add an observer

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef Observer, CFRunLoopActivity activity) {}); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); CFRelease(observer);Copy the code

RunLoop with thread

The source address

CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; if (! __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); return __main; } CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); if (! __CFRunLoops) { __CFUnlock(&loopsLock); / / create a store all runloop object dictionary CFMutableDictionaryRef dict = CFDictionaryCreateMutable (kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); Runloop CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (! Loop) {// Create a new runloop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); Loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (! Loop) {// Store runloop CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } __CFUnlock(&loopsLock); CFRelease(newLoop); } return loop; }Copy the code

Through the above source code we can see

  • There is one and only one thread per threadrunloopobject
  • All of the apprunloopStored in a global Dictionary, thread for key, runloop for value
  • The thread didn’t exist when it was createdrunloopObject,runloopWill be created when it is first fetched. The main thread of therunloopThe child thread does not get it by defaultrunloop.
  • runloopIs destroyed at the end of the thread.

RunLoop Execution process

Loop SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); / / mode does not exist or the source0, source1, timer, the observer is not returning kCFRunLoopRunFinished end loop if (NULL = = currentMode | | __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { Boolean did = false; if (currentMode) __CFRunLoopModeUnlock(currentMode); __CFRunLoopUnlock(rl); return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; } volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl); CFRunLoopModeRef previousMode = rl->_currentMode; Rl ->_currentMode = currentMode; int32_t result = kCFRunLoopRunFinished; If (currentMode->_observerMask & kCFRunLoopEntry) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); / / to do specific result = __CFRunLoopRun (rl, currentMode, seconds, returnAfterSourceHandled, previousMode); If (currentMode->_observerMask & kCFRunLoopExit) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); Rl ->_currentMode = previousMode; return result; } /* RL runloop RLM run mode seconds timeout stopAfterHandle ture: PreviousMode runloop last mode */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { int32_t retVal = 0; Do {// notify the observer that timer if (RLM ->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers); Source if (RLM ->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeSources); // Handle block __CFRunLoopDoBlocks(rl, RLM); Source0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, RLM, stopAfterHandle); If (sourceHandledThisLoop) {// Handle block __CFRunLoopDoBlocks(rl, RLM); } msg = (mach_msg_header_t *)msg_buffer; If (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {// goto handle_msg goto handle_msg; } // Notify the Observer that it is about to hibernate if (! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); / / dormancy __CFRunLoopSetSleeping (rl); CFAbsoluteTime sleepStart = poll ? 0.0: CFAbsoluteTimeGetCurrent (); // Call mach_msg and wait for a message to accept mach_port. The thread will sleep until it is awakened by one of the following events. // - a port-based Source1 event. __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer)), &livePort) { mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive MSG} // thread wait for receive MSG} // Notify the Observer to end hibernation if (! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); // if you have Source1 to process, go to handle_msg:; if (modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// CFRUNLOOP_WAKEUP_FOR_TIMER(); if (! __CFRunLoopDoTimers(RL, RLM, mach_absolute_time())) {timer __CFArmNextTimerInMode(RLM, rL); } } else if (rlm->_timerPort ! = MACH_PORT_NULL && livePort == RLM ->_timerPort) {CFRUNLOOP_WAKEUP_FOR_TIMER(); if (! __CFRunLoopDoTimers(RL, RLM, mach_absolute_time())) {timer __CFArmNextTimerInMode(RLM, rL); }} else if (livePort == dispatchPort) {GCD Async To Main Queue __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } else {// Wake up by source1 CFRUNLOOP_WAKEUP_FOR_SOURCE(); voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release); CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); if (rls) { mach_msg_header_t *reply = NULL; Source1 sourceHandledThisLoop = __CFRunLoopDoSource1(rl, RLM, RLS, MSG, MSG ->msgh_size, &reply) || sourceHandledThisLoop; } } if (msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg); // Handle Block __CFRunLoopDoBlocks(rl, RLM); RetVal if (sourceHandledThisLoop && stopAfterHandle) {//// If the event is processed, Starts the Runloop set parameters for one-time implementation, setting while parameters exit Runloop retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { Runloop retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) {// Externally forced exit __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty (rl, RLM, previousMode)) {/ / in the runloop source0 / source1 / timer/observer all have no, Exit runloop retVal = kCFRunLoopRunFinished; } } while (0 == retVal); return retVal; }Copy the code

The general process is as follows: __CFRunLoopRunThe subsequent functions will eventually be called to do the work.