Runloop preface
When run with runtime, runloop is an object and is a do… While runs a loop, which is used to keep the program running. It handles various events in the program, including touches, timers, messages, etc
void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; Do {result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code
There are five Ref structures in runloop:
CFRunLoopRef, CFRunLoopModeRef, CFRunLoopSourceRef, CFRunLoopTimerRef, CFRunLoopObserverRef
The runloopRef structure is shown below, with threads, multiple modes held, and so on
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef; //CFRunLoopRef typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef; //CFRunLoopSourceRef typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef; //CFRunLoopObserverRef typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef; //CFRunLoopTimerRef typedef struct __CFRunLoopMode *CFRunLoopModeRef; 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
Runloop in relation to threads
Each thread has a unique runloop corresponding to it. Generally, the creation thread is created. The only difference is that the main thread runloop is enabled by default, while the child thread runloop is disabled by default
CFRunLoopRef CFRunLoopGetMain(void) {CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed if (! __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; }Copy the code
Runloop is more like keeping the thread running than keeping the program running, so if the child thread does not actively start runloop, the current task will be completed, and the thread will terminate
// create a child thread, run ruloop, Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSRunLoop currentRunLoop] run]; if (isStop) { [NSThread exit]; }});Copy the code
The following diagram, which uses the official runloop in action, shows what a runloop does in a thread: it receives events from the input source and timer source, and then processes them in the thread
!
The runloop item
Runloop handles the following six types of events, also known as runloop items, i.e
GCD main dispatch queue __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ //Observer events that are notified of state changes in runloop. // Block event, non-delayed NSObject PerformSelector called immediately, dispatch_after called immediately, The block callback __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ responds to source0 and handles events such as UIEvent and CFSocket. Manual trigger is required. Touch events are actually Source1 after receiving system in the callback __IOHIDEventSystemClientQueueCallback triggered Source0 within (), Source0 again trigger _UIApplicationHandleEventQueue (). Source0 must wake up the runloop to respond and execute in time. If the runloop is asleep waiting for the mach_msg event from the system, The runloop is then awakened by source1 to execute __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ // source1, Handle the mach_msg event __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ // Timer event, delayed NSObject PerformSelector, Delayed dispatch_after, timer event __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__Copy the code
For example, the main queue, timer, block calls:
// dispatch_async(dispatch_get_main_queue(), ^{}); //timer [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { }]; //block void (^block)(void) = ^ { }; block();Copy the code
The item structure of the block is shown below, and its block is called when it needs to be executed
struct _block_item {
struct _block_item *_next;
CFTypeRef _mode; // CFString or CFSet
void (^_block)(void);
}; //blockItem
Copy the code
runLoopMode
As we know, runloop has a one-to-one relationship with thread, and runloop mainly responds to 6 events, namely its item. In addition, each runloop object corresponds to multiple Runloopmodes, and each runLoopMode represents a running mode, as shown below:
KCFRunLoopDefaultMode: The default Mode of the App, in which the main thread is normally run.
UITrackingRunLoopMode: interface tracing Mode, used by ScrollView to track touch sliding, ensuring that the interface sliding is not affected by other modes.
UIInitializationRunLoopMode: in the first to enter the first Mode when just start the App, start after the completion of the will no longer be used.
GSEventReceiveRunLoopMode: accept system internal Mode of events, usually in less than.
Kcfrunloopcommonmode: this is a placeholder Mode. When set to this Mode, events will respond properly in all of the above modes.
The running mode of runloop changes depending on the scenario. By default, it runs in kCFRunLoopDefaultMode. When the scrolling view slides, mode switches to UITrackingRunLoopMode
In addition, each runLoopMode is associated with multiple items, such as Source (source0, source1), observers, timers, etc., which are one-to-many
The structure of mode is defined as CFRunLoopModeRef, and the 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; //source source set CFMutableSetRef _sources1; CFMutableArrayRef _observers; // Observer source collection Array CFMutableArrayRef _timers; // Array 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
Therefore, the relationship between Runloop, thread, mode, source, timer and observer is shown in the figure below
Analyze the running logic of runloopMode with __CFRunLoopDoBlocks source code
After going through CFRunLoopRun, it goes to the CFRunLoopRunSpecific method, and then goes to __CFRunLoopRun, which tells us the actual runloop logic, the execution of various item events. Here we use the event code of the block, To analyze the item execution of runloopMode
The execution code for block __CFRunLoopDoBlocks is shown below
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked if (! rl->_blocks_head) return false; if (! rlm || ! rlm->_name) return false; Boolean did = false; struct _block_item *head = rl->_blocks_head; struct _block_item *tail = rl->_blocks_tail; rl->_blocks_head = NULL; rl->_blocks_tail = NULL; CFSetRef commonModes = rl->_commonModes; CFStringRef curMode = rlm->_name; __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); Struct _block_item *prev = NULL; struct _block_item *item = head; Struct _block_item *curr = item; struct _block_item *curr = item; Item = item->_next; Boolean doit = false; If (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {// The timer adds the same mode as the current runloop mode // curr->_mode = KCFRunLoopCommonModes equal / / mode existing in the current time to achieve the above conditions can be executed doit is true doit = CFEqual (curr - > _mode, curMode) | | (CFEqual (curr - > _mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode)); } else {// whether the inclusion relation is checked, Actual logic in accordance with the above doit = CFSetContainsValue ((CFSetRef) curr - > _mode, curMode) | | (CFSetContainsValue ((CFSetRef) curr - > _mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode)); } if (! doit) prev = curr; If (doit) {if (prev) prev->_next = item; if (curr == head) head = item; if (curr == tail) tail = prev; void (^block)(void) = curr->_block; // Clears modes and items, loop CFRelease(curr->_mode); free(curr); // Execute the current block if (doit) {__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); did = true; } // Release the current block Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc } } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); if (head) { tail->_next = rl->_blocks_head; rl->_blocks_head = head; if (! rl->_blocks_tail) rl->_blocks_tail = tail; } return did; }Copy the code
In the timer, for example we will add a time to the current runloop, one can choose to defaultMode or commonMode, usually call __CFRunLoopAddItemToCommonModes method to add a new mode and item
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) { CFStringRef modeName = (CFStringRef)value; CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]); CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]); if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) { CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName); } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) { CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName); } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) { CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); } } void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) { CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return; if (! __CFIsValid(rlt) || (NULL ! = rlt->_runLoop && rlt->_runLoop ! = rl)) return; __CFRunLoopLock(rl); if (modeName == kCFRunLoopCommonModes) { CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; if (NULL == rl->_commonModeItems) { rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); // add item CFSetAddValue(rl->_commonModeItems, RLT); if (NULL ! = set) { CFTypeRef context[2] = {rl, rlt}; /* add new item to all common-modes */ / timer -- items() (__CFRunLoopAddItemToCommonModes), (void *)context); CFRelease(set); } } else { ... } __CFRunLoopUnlock(rl); }Copy the code
CFRunLoopTimerRef
CFRunLoopTimerRef object, whose structure is shown below
struct __CFRunLoopTimer { CFRuntimeBase _base; uint16_t _bits; pthread_mutex_t _lock; CFRunLoopRef _runLoop; // The current runloop CFMutableSetRef _rlModes; CFAbsoluteTime _nextFireDate; CFTimeInterval _interval; /* immutable */ interval CFTimeInterval _tolerance; /* mutable */ uint64_t _fireTSR; /* TSR units */ CFIndex _order; /* immutable */ CFRunLoopTimerCallBack _callout; /* immutable */ CFRunLoopTimerContext _context; /* immutable, except invalidation */ };Copy the code
CFRunLoopTimerRef implements the underlying NSTimer object, and its mode is also affected by mode switching, for example, using the following code
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"123");
}];
Copy the code
The generated source code is shown below
(void)createCFTimer { CFRunLoopTimerContext context = { 0, ((__bridge void *)self), NULL, NULL, NULL }; CFRunLoopRef rlp = CFRunLoopGetCurrent(); // Get the runloop object of the current thread /** Create the CFRunLoopTimerRef object Parameter 5: Priority of the CFRunLoopObserver When there are multiple CFRunloopObservers in the same Runloop phase normal use 0 Parameter 6: callback, such as triggering events, I will come here parameter 7: Context log information */ CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, runLoopTimerCallBack, &context); // Add it to the current runloop, default is kCFRunLoopDefaultMode CFRunLoopAddTimer(RLP, timerRef, kCFRunLoopDefaultMode); Void runLoopTimerCallBack(CFRunLoopTimerRef timer, void *info){}Copy the code
CFRunLoopObserverRef
The objects are shown below
struct __CFRunLoopObserver { CFRuntimeBase _base; pthread_mutex_t _lock; CFRunLoopRef _runLoop; CFIndex _rlCount; CFOptionFlags _activities; /* immutable */ CFIndex _order; /* immutable */ CFRunLoopObserverCallBack _callout; /* immutable */ CFRunLoopObserverContext _context; /* immutable, except invalidation */ session};Copy the code
The activity status activityes(CFRunLoopActivity) is shown below, which indicates the status of the runloop running to a certain stage. You can refer to the runloop running process to understand it
Typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry, // enter loop kCFRunLoopBeforeTimers, // The Timer callback kCFRunLoopBeforeSources is about to be triggered, the Source0 callback kCFRunLoopBeforeWaiting is about to be triggered, // Wait for mach_port message to enter sleep kCFRunLoopAfterWaiting), // Exit loop kCFRunLoopAllActivities // loop all state changes}Copy the code
An observer is an observer that observes the state change of the Runloop and calls back the specified function, as shown below
CFRunLoopObserverContext context = {0, ((__bridge *)self), NULL, NULL, NULL }; Runloop CFRunLoopRef RLP = CFRunLoopGetCurrent(); /** Parameter 1: memory used to allocate the object parameter 2: CFRunLoopActivity type parameter 3: whether CFRunLoopObserver is looped Parameter 4: priority of CFRunLoopObserver When there are multiple CFRunloopObservers in the same Runloop phase, normally 0 is used. 5: callback, such as triggering events, I will come here. 6: Context log information */ CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, runLoopObserverCallBack, &context); // Add it to the current runloop in mode kCFRunLoopDefaultMode CFRunLoopAddObserver(RLP, observerRef, kCFRunLoopDefaultMode); Void runLoopObserverCallBack(CFRunLoopObserverRef Observer, CFRunLoopActivity activity, void *info){}Copy the code
CFRunLoopSourceRef
CFRunLoopSourceRef is the source, which is divided into source0 and source1. It is a union structure (sharing a piece of memory), indicating that source0 and source1 have the same structure. According to their application, the structure is as follows:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
Copy the code
source0
Handle App internal events and manage (trigger) by App itself, for example, UIEvent CFSocket
- (void)source0 {// Create the source session layer. CFRunLoopSourceContext Context = {0, NULL, NULL, NULL, NULL, NULL, schedule, Cancel, perform,}; /** Argument one: Pass NULL or kCFAllocatorDefault to use the current default allocator. Parameter two: Priority index indicating the order in which run loop sources are processed. */ CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context); CFRunLoopRef rlp = CFRunLoopGetCurrent(); CFRunLoopAddSource(RLP, source0, kCFRunLoopDefaultMode); // An execution signal, marked as pending CFRunLoopSourceSignal(source0); CFRunLoopWakeUp(RLP); CFRunLoopWakeUp(RLP); // Cancel removing CFRunLoopRemoveSource(RLP, source0, kCFRunLoopDefaultMode); CFRelease(rlp); } oid schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){NSLog(@" preparation "); } void perform(void *info){NSLog(@" handle event "); } void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){NSLog(@" cancel "); }Copy the code
source1
Contains a mach_port and a callback (function pointer)
NSPort communication is used to send messages to each other through the kernel and other threads, which is not covered here
Runloop Runs the process
The ref object of the runloop is the thread, mode, source, timer, observer, etc., and the corresponding relationship between some items and the use of item events in __CFRunLoopDoBlocks
Let’s take a look at the runloop source code and see what it does
As mentioned earlier, the main event is the internal implementation of the __CFRunLoopRun function. Since the internal logic is very long, here we take out the main code logic to view (some deleted).
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { int32_t retVal = 0; Do {/// notify Observers: about to handle the timer event __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers); / / / notify Observers: __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeSources); Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, RLM, stopAfterHandle); __CFRunLoopDoBlocks(rl, RLM); If (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {/// handle message goto handle_msg; } /// Notify Observers: about to enter hibernation __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); /// notify Observers: astounded, ending dormancy __CFRunLoopDoObservers(rl, RLM, kCFRunLoopAfterWaiting); Handle_msg: if (wakeup_for_timer) {CFRUNLOOP_WAKEUP_FOR_TIMER(); // Handle Timers __CFRunLoopDoTimers(rl, RLM, mach_absolute_time()); } else if (wake up by GCD) {CFRUNLOOP_WAKEUP_FOR_DISPATCH(); // handle GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); } else if (wake up by Source1) {CFRUNLOOP_WAKEUP_FOR_SOURCE(); // wake up by Source1, Source1 __CFRunLoopDoSource1(rl, RLM, RLS, MSG, MSG ->msgh_size, &reply)} // Handle block __CFRunLoopDoBlocks(rl, RLM); 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
From the above code, you can see the runloop running through the following steps
1. Inform the Observer that the loop is about to enter
2. Notify the Observer that the Timer event will be processed
3. Inform the Observer that Source0 will be processed
4. Deal with blocks
5. Deal with source0
6. If there is source1, go to Step 10
7. Inform the Observer that sleep is about to be set
8. Sleep and wait to be awakened
9. Inform the Observer that the thread has just been awakened
10. Process the message received while waking up, and skip back to Step 2
11. Inform the Observer that runloop is about to exit
Listen for runloop callbacks
You can listen to the execution of the runloop using the following method
// Add a listener
- (void)addRunLoopObserver {
1. Create a listener
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"Enter the RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"Timer event about to be processed");
break;
case kCFRunLoopBeforeSources:
NSLog(@"Source event about to be processed");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"Going to sleep.");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"Awakened");
break;
case kCFRunLoopExit:
NSLog(@"Quit the RunLoop");
break;
default:
break; }});// 2. Add listeners
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
Copy the code
The application of the runloop
As you can see from the above flow, runloop switches mode depending on the usage scenario
Note:
Therefore, it is necessary to pay attention to the use of timer. Put the timer in the corresponding mode as required. If you want to run it at any time, you can set the mode to commoneMode
Image loading optimization:
When loading a large image asynchronously, the rendering will be slow (this process must be in the main thread and the same is true for sliding). When scrolling a large image quickly, the rendering will be slow. Therefore, you can use runloop’s defalutMode to render the image. Avoid the problem of fast sliding lag (note that this process is the process of setting image, not downloading)
Monitoring main thread is stuck:
Add an Observer to the main thread to monitor the state of the runloop. Normally, the runloop is either on the way to processing events or sleeping. Once the state of kCFRunLoopBeforeSources before sleep is found or the state of kCFRunLoopAfterWaiting after wake is found and has not changed within the set time threshold (i.e. Stuck before source processing after wake up, between steps 8->4, not 4->8).
Try it yourself