It started on a personal blog
What is the RunLoop
What is runloop? Runloop is what it sounds like. It’s basically a loop, but it’s an advanced loop. While a normal while loop causes the CPU to wait in a busy state, Runloop is an “idle” wait, which is analogous to epoll under Linux. When no event occurs, the Runloop goes to sleep. When an event occurs, the Runloop goes to the corresponding Handler to handle the event. Runloop keeps threads busy when they need to do something, and puts them to sleep when they don’t
Before you start, think about these interview questions
- How does runloop relate to threads?
- Relation between Timer and Runloop?
- If I add an NSTimer that responds every 3 seconds, when I drag the tableview, the timer may not respond. What can I do?
- Implementation logic inside Runloop?
- How does runloop respond to user actions?
- Talk about several states of runLoop
- What is the mode of runloop?
Source code analysis
Before answering questions, let’s look at the source code
Opensource.apple.com/tarballs/CF RunLoop source code… Download the latest CF-1153.18.tar.gz(the latest version at the time of writing)
View the source code
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
Copy the code
through
_CFRunLoopGet0 obtainedCopy the code
Go in and see what’s done
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if(! loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; }Copy the code
If the loop is empty, create a runloop for the key from this thread
Summary:
- 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 thread is created without a RunLoop object; the RunLoop is created the first time it gets it
- The RunLoop will be destroyed at the end of the thread.
The runloop mode
Let’s take a look at the five classes for Runloop in Core Foundation
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
Copy the code
Take a look at the runloop structure
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
Only the main ones are left
typedef struct __CFRunLoop * CFRunLoopRef; struct __CFRunLoop { pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; CFMutableSetRef _modes; };Copy the code
CFRunLoopRef contains _modes, which are a set of CFRunLoopModeRef modes. Only one of these modes is the current mode, called _currentMode
Now let’s look at what’s in runloopMode, and again, just keep the main ones, the key ones are the following four, right
To sum it up
- CFRunLoopModeRef indicates the running mode of RunLoop
- A RunLoop contains several Mode, each Mode and contains several Source0 / Source1 / Timer/Observer
- Only one of these modes can be selected as currentMode when RunLoop starts
- If you need to switch Mode, you can only exit the Loop and select another Mode to enter
- Different groups of Source0 / Source1 / Timer/Observer can be separated, each other
- If there is no any Source0 / Source1 / Timer Mode/Observer, RunLoop immediately exit
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
Copy the code
There are many modes in RunLoop, but only one is currently running, represented by a graph
The logic used in a particular type of runloop is shown below
So, what exactly do _sources0, _sources1, _observers, _timers J contain? Sum it up with a picture and then go into details
As shown in the above, _sources0 contains touch events, and the performSelector: onThread: run the code certificate, first create a new project, realize the click event, NSLog just to breaking point
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"This is printed just to break the dots."); After the} @end breakpoint is paused, enter the LLDB instruction bt as shown in the figure belowCopy the code
The SOURCE0 method __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ is called from the print log, so verify performSelector
So let’s look at the Timer again
For the other cases, the reader will verify.
Use a diagram to summarize the logic of RunLoop
Source code internal detail analysis
If you want to analyze the source code, you should first know where the entry is
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); // notify Observers: enter Loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); Result = __CFRunLoopRun(rl, currentMode, seconds,returnAfterSourceHandled, previousMode); // notify Observers: exit Loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);return result;
}
Copy the code
__CFRunLoopRun = __CFRunLoopRun = __CFRunLoopRun
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do{// notify Observers: about to handle Timers __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers); // notify Observers: about to deal with Sources __CFRunLoopDoObservers(Rl, RLM, kCFRunLoopBeforeSources); // Handle Blocks __CFRunLoopDoBlocks(rl, RLM); / / Sources0 processingif(__CFRunLoopDoSources0(rl, RLM, stopAfterHandle)) {__CFRunLoopDoBlocks(rl, RLM); } Boolean poll =sourceHandledThisLoop || (0ULL == timeout_context->termTSR); // Check whether there issource1
if(__CFRunLoopServiceMachPort(dispatchPort, & MSG, sizeof(MSg_buffer), &livePort, 0, &voucherState, NULL)) {// If there is onesource1 goto handle_msg goto handle_msg; } // Notify Observers: about to hibernate __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); // Notify Observers: end hibernation __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
handle_msg:;
if{// Handle Timers __CFRunLoopDoTimers(rl, RLM, mach_absolute_time())}else if{// Handle GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); }else{/ / to be here, that means be awakened Source1 __CFRunLoopDoSource1 (rl, RLM, RLS, MSG, MSG - > msgh_size, & reply) | |sourceHandledThisLoop; // Handle Blocks __CFRunLoopDoBlocks(rl, RLM); // Set the return value to decide whether to continue the loopif (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;
}
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
} while (0 == retVal);
return retVal;
}
Copy the code
This is what it looks like in a screenshot
When the LLDB debugs the stack, how do these methods get called? Here uses __CFRunLoopDoTimers as an example
There are five known modes
KCFRunLoopDefaultMode: the default Mode of the App, usually the main thread is run in this Mode UITrackingRunLoopMode: Interface tracking Mode, to ScrollView tracking touch sliding, guarantee the interface slip is not affected by other Mode UIInitializationRunLoopMode: When just start the App the first to enter the first Mode, after the completion of the start will no longer use GSEventReceiveRunLoopMode: accept system internal Mode of events, usually in less than kCFRunLoopCommonModes: This is a placeholder Mode, not a real ModeCopy the code
The state of the RunLoop
/* Run Loop Observer Activities */ 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), // About to exit Loop kCFRunLoopAllActivities = 0x0FFFFFFFU};Copy the code
Two common modes
-
KCFRunLoopDefaultMode (NSDefaultRunLoopMode) : The default Mode of the App, in which the main thread usually runs
-
UITrackingRunLoopMode: interface tracing Mode, used by ScrollView to track touch sliding, ensuring that the interface sliding is not affected by other modes
RunLoop and NSTimer
We know that the NSTimer timer, by default, will be interrupted by the UIScrollView, which will affect the use of the timer. The reason is that when you scroll, the RunLoop switches to UITrackingRunLoopMode, but the timer is in NSDefaultRunLoopMode, so it stops. The solution is to set NSRunLoopCommonModes. Special attention should be paid to:
NSRunLoopCommonModes are not really modes, they are just tokens
References for this article:
RunLoop official source code
Basic principles of iOS
For more information, please pay attention to the personal public number