OC Basic principles of exploration document summary

Runloops keep threads running and allow them to respond and handle events at any time.

Main Contents:

  1. The understanding of the Runloop
  2. Runloop object awareness
  3. Runloop execution process
  4. The specific use

Runloop: Runloop: Runloop: Runloop

1. Understanding Runloop

1.1 What is a RunLoop

A Runloop is essentially an object that implements an event loop mechanism that allows the thread to respond to and handle events at any time.

A Runloop can be thought of as a loop of idle do-while. It is running like a normal do-while loop, but running while waiting does not cost CPU performance.

Function:

  • Keep the program running
    • When the program starts, it opens a main thread, which runs a RunLoop corresponding to the main thread. The RunLoop ensures that the main thread will not be destroyed, thus ensuring that the program continues to run
  • Save CPU resources and improve program performance
    • Threads sleep when there is no message processing and wake up as soon as a message arrives
  • Respond to events
    • Timer, method call PerFormSelecotr
    • GCD Async Main Queue
    • Event response, gesture recognition, interface refresh
    • Network request
    • Automatic release pool autoreleasePool

1.2 Idle verification

RunLoop saves resources and is an idle do… While looking at both types of CPU, RunLoop execution loops do not use CPU resources, while do… The while is always using CPU resources

Normally the do… while

Runloop cycle

1.3 Starting the main thread Runloop

We know that a Runloop can keep the thread running continuously. In iOS, an application can keep running continuously. This means that the main thread Runloop does work.

Since the main thread needs to run continuously once it is started, it is assumed that the Runloop was started when the main thread was started. So we look in the main function at the entrance to the program

You can’t see the implementation when you go into UIApplicationMain

You can actually execute RunLoop by printing on the console after startup

2. Understanding Runloop objects

I’ll look at the source code to see what Runloop is underneath,

We use NSRunloop on top, which is part of the Foundation framework. However, when printing the stack, you can see that NSRunLoop is actually the encapsulation of CFRunLoopRef based on the CoreFoundation framework. So we need to analyze the CFRunLoopRef object in the CoreFoundation source code.

Such as:

2.1 CFRunLoopRef object

CFRunLoopRef is the Runloop itself, the underlying implementation of NSRunloop

Source:

struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // // The CFRunLoopWakeUp function is used by the kernel to send a message to the port to wake up runloop Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; // Uint32_t _winthread; CFMutableSetRef _commonModes; // Its commonModes CFMutableSetRef _commonModeItems; // commonModeItems CFRunLoopModeRef _currentMode; // Current mode CFMutableSetRef _modes; // All modes struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFTypeRef _counterpart; };Copy the code

Description:

  • You can see that Runloop has its own thread
  • It contains both commonModes and commonModeItems. This can be called a common mode, and it contains other modes, as discussed below
  • A Runloop contains multiple modes and can retrieve the current mode
  • Thus Runloop and mode are one-to-many, but only one mode can be used at a time

2.2 CFRunLoopModeRef object

The CFRunLoopModeRef object is the running mode of Runloop

typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; CFStringRef _name; Boolean _stopped; // Whether mode is terminated char _padding[3]; / / the most core part of the whole structure -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - CFMutableSetRef _sources0; // Event source 0 CFMutableSetRef _sources1; // Event source 1 CFMutableArrayRef _observers; // timers CFMutableArrayRef _timers; / / timer -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - CFMutableDictionaryRef _portToV1SourceMap; // the dictionary key is mach_port_t and the value is CFRunLoopSourceRef __CFPortSet _portSet; _wakeUpPort and _timerPort are stored in the array CFIndex _observerMask; #if USE_DISPATCH_SOURCE_FOR_TIMERS dispatch_source_t _timerSource; dispatch_queue_t _queue; Boolean _timerFired; 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

Description:

  • Modes are identified by name, not by ID or the like
  • Mode can store four items: _sources0, _sources1, _observers, and _timers
  • You can store multiple items of each type.
  • _sources0 and _sources1 are stored by the Set structure
  • Observers _observers and _timers are stored in an array structure

Note:

  • Therefore, we can conclude that a Runloop can have multiple modes, but only one mode can be allowed at a time, and modes can have multiple events. There are four types: event source 0, event source 1, listener, and time source.
  • Runloop manages mode, which manages specific events
  • Only one mode can be selected when Runloop starts
  • If you need to switch mode, you can only exit the current loop and enter the loop again after selecting mode
  • If there are no _sources0, _sources1, _observers, or _timers in mode, Runloop exits immediately.
  • We cannot create a mode by ourselves. When a new mode Name is passed in and the Runloop finds no corresponding mode, the corresponding CFRunLoopModeRef is automatically created.
  • For a Runloop, internal modes can only be added, not removed

2.2.1 The five operating modes of __CFRunLoopMode

1. KCFRunLoopDefaultMode: the default Mode of the App, in which the main thread usually runs Interface tracking Mode, to track ScrollView touch sliding, guarantee the interface slip is not affected by other Mode 3. UIInitializationRunLoopMode: When just start the App the first to enter the first Mode, after the completion of the start will no longer use, will switch to the 4 kCFRunLoopDefaultMode GSEventReceiveRunLoopMode: 5. Kcfrunloopcommonmode: This is a general Mode, used to flag kCFRunLoopDefaultMode and UITrackingRunLoopMode, not a true ModeCopy the code
  • Kcfrunloopcommonmode is not a true mode, but a general mode. NSRunLoopCommonModes is actually a collection of the Mode, including NSDefaultRunLoopMode and NSEventTrackingRunLoopMode by default
  • There are five modes mentioned in Apple documents, while only NSDefaultRunLoopMode and NSRunLoopCommonModes are exposed publicly in iOS.

2.2.2 CommonModes understanding

CommonModes need to be analyzed separately because they have their own special role

Let’s see what happens when we add _commonModes to Runloop

Source:

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) { if (! CFSetContainsValue(rl->_commonModes, modeName) {CFSetRef set = rl->_commonModeItems? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL; // Get all _commonModes CFSetAddValue(rl->_commonModes, modeName); if (NULL ! = set) { CFTypeRef context[2] = {rl, modeName}; / / add all _commonModeItems one by one to each Mode in the _commonModes CFSetApplyFunction (set, (__CFRunLoopAddItemsToCommonMode), (void *)context); CFRelease(set); }}}Copy the code

Description:

  • You can see that when commonMode is added to a Runloop, the Runloop automatically synchronizes the Source/Observer/Timer in _commonModeItems to all modes with the Common flag
  • That is, when an event of another mode is marked common, the event is in common mode at the same time.
  • So commonMode can also contain events in other modes. That’s what public mode means.

2.2.3 ModeItem

ModeItem is the event item included in mode, including _sources0, _sources1, _observers, and _timers.

  • The Runloop needs to process the following messages: _sources0, _sources1, and _timers
  • The object Runloop is using to listen for state: _observers

API:

// Add source event source CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName); // Add listener CFRunLoopAddObserver(CFRunLoopRef RL, CFRunLoopObserverRef Observer, CFStringRef modeName); // Add time source CFRunLoopAddTimer(CFRunLoopRef RL, CFRunLoopTimerRef timer, CFStringRef mode); // Remove event source CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName); // Remove listener CFRunLoopRemoveObserver(CFRunLoopRef RL, CFRunLoopObserverRef Observer, CFStringRef modeName); CFRunLoopRemoveTimer(CFRunLoopRef RL, CFRunLoopTimerRef timer, CFStringRef mode);Copy the code

Note:

  • A mode can contain multiple ModeItems, and a modeItem can also be added to multiple modes at the same time. However, when an item is added to the same mode repeatedly, only one modeItem exists
  • If there is no item in a mode, the Runloop exits without entering the loop
  • Mode implements events externally through modeItem

2.2.4 Simple use of mode

Let’s see if NSTimer is not accurate.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{NSLog(@"WY: sliding %@",[NSRunLoop currentRunLoop].currentMode); } - (void) scrollViewDidEndDecelerating (scrollView UIScrollView *) {NSLog (@ "WY: stop sliding % @", [NSRunLoop currentRunLoop].currentMode); } - (void) Touch began :(NSSet< uittouch *> *) Touches withEvent:(UIEvent *) Event {NSLog(@"WY: touch %@",[NSRunLoop currentRunLoop].currentMode); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // [NSTimer ScheduledTimerWithTimeInterval: 2.0 target: self selector: @ the selector (show) the userInfo: nil repeats: YES]; NSTimer * timer = [NSTimer timerWithTimeInterval: 2.0 target: self selector: @ the selector (show) the userInfo: nil repeats: YES]; // Add the timer to the RunLoop. And select the default running mode NSDefaultRunLoopMode = kCFRunLoopDefaultMode // [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; TextFiled, timer invalid, stop sliding, Timer recovery // Cause: textFiled When textFiled slips, the Mode of RunLoop will automatically switch to UITrackingRunLoopMode, so the timer fails. When sliding stops, RunLoop will switch back to NSDefaultRunLoopMode. // 2. When we add the timer to the UITrackingRunLoopMode mode, At this time only when we are sliding textField timer will run / / [[NSRunLoop mainRunLoop] addTimer: timer forMode: UITrackingRunLoopMode]; // 3. How does timer work in both modes? // 3.1 Adding a timer in both modes is ok, but adding a timer twice is not the same timer All NSRunLoopCommonModes will work. The following two modes are tagged //0: <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"} //2 : <CFString 0x10A8E85e0 [0x10A8C7a40]>{contents = "kCFRunLoopDefaultMode"} Timer can be used in UITrackingRunLoopMode, KCFRunLoopDefaultMode two mode [[NSRunLoop mainRunLoop] addTimer: timer forMode: NSRunLoopCommonModes]; NSLog(@"%@",[NSRunLoop mainRunLoop]); } -(void)show { NSLog(@"-------"); }Copy the code

Description:

  • You can see that when we use kCFRunLoopDefaultMode, when we slide the ScrollView, the NSTimer pauses
  • When we use the UITrackingRunLoopMode we can only run NSTimer when sliding the ScrollView.
  • When we use NSRunLoopCommonModes, sliding or not sliding the ScrollView will run NSTimer.
  • This is because the items of kCFRunLoopDefaultMode and UITrackingRunLoopMode are in modeItems of NSRunLoopCommonModes by default

2.3 CFRunLoopSourceRef object

CFRunLoopSourceRef is an object that responds to events and responds when an event occurs

Source:

typedef struct __CFRunLoopSource * CFRunLoopSourceRef; struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; pthread_mutex_t _lock; CFIndex _order; CFMutableBagRef _runLoops; // Contains multiple runloops // versions union {CFRunLoopSourceContext version0; /* immutable, except invalidation */ CFRunLoopSourceContext1 version1; /* immutable, except invalidation */ } _context; };Copy the code

Description:

  • The __CFRunLoopSource structure has two types: CFRunLoopSourceContext and CFRunLoopSourceContext1.
  • A union is one or the other.

2.3.1 source0

Source0 is used to implement user events

Source:

typedef struct { CFIndex version; void * info; const void *(*retain)(const void *info); void (*release)(const void *info); CFStringRef (*copyDescription)(const void *info); Boolean (*equal)(const void *info1, const void *info2); CFHashCode (*hash)(const void *info); void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode); void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode); void (*perform)(void *info); } CFRunLoopSourceContext;Copy the code

Description:

  • Source0 contains only one callback (function pointer) and does not actively fire events.
  • To use this, you need to call CFRunLoopSourceSignal (source), mark the source as pending, and then manually call CFRunLoopWakeUp (runloop) to wake up the Runloop to handle the event.

2.3.2 source1

Source1 is used to implement communication between system events and port-based threads. Source1 can actively trigger events and actively wake up events

Source:

typedef struct { CFIndex version; void * info; const void *(*retain)(const void *info); void (*release)(const void *info); CFStringRef (*copyDescription)(const void *info); Boolean (*equal)(const void *info1, const void *info2); CFHashCode (*hash)(const void *info); #if TARGET_OS_OSX || TARGET_OS_IPHONE mach_port_t (*getPort)(void *info); void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info); #else void * (*getPort)(void *info); Void (*perform)(void *info); #endif} CFRunLoopSourceContext1;Copy the code

Description:

  • Source1 contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads.
  • This Source actively wakes up the RunLoop thread

Conclusion:

2.4 CFRunLoopTimerRef object

CFRunLoopTimerRef is a time-based trigger that is a time source

Source:

typedef struct __CFRunLoopTimer * CFRunLoopTimerRef; struct __CFRunLoopTimer { CFRuntimeBase _base; uint16_t _bits; pthread_mutex_t _lock; CFRunLoopRef _runLoop; // Runloop CFMutableSetRef _rlModes; CFAbsoluteTime _nextFireDate; CFTimeInterval _interval; /* immutable */ CFTimeInterval _tolerance; /* mutable */ uint64_t _fireTSR; /* TSR units */ CFIndex _order; /* immutable */ CFRunLoopTimerCallBack _callout; // The timer callback CFRunLoopTimerContext _context; // Context object};Copy the code
  • CFRunLoopTimerRef is a time-based trigger that also has a callback function.
  • Runloop can be woken up to execute the callback when the time point is reached

Conclusion:

2.5 CFRunLoopObserverRef object

CFRunLoopObserverRef is the Observer, and each Observer contains a callback (function pointer) that the Observer receives when the state of the RunLoop changes.

Source:

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef; struct __CFRunLoopObserver { CFRuntimeBase _base; pthread_mutex_t _lock; CFRunLoopRef _runLoop; // Listen to RunLoop CFIndex _rlCount; // Add the number of RunLoop objects to the Observer CFOptionFlags _activities; /* immutable */ CFIndex _order; / / only to monitor at the same time a CFRunLoopObserverCallBack _callout; // Listen to the callback CFRunLoopObserverContext _context; // Context for memory management};Copy the code

The time points of observation are as follows

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 };Copy the code

Conclusion:

Simple use:

- (IBAction)clickTest:(id)sender {// Create child thread and start NSThread * Thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil]; thread.name = @"wyThread"; self.thread = thread; [self.thread start]; } // Add a RunLoop -(void)show {// note: Print before the RunLoop is created and running. If you print after the RunLoop is running, the RunLoop will run first and will not be able to get out of the loop. NSLog(@"%s",__func__); NSLog(@"%s",__func__); NSLog(@"%s",__func__); // 1. Create a child RunLoop within the child thread, with at least one Timer or Source in the RunLoop to ensure that the RunLoop does not exit due to idling. [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] Source [NSMachPort port] forMode:NSDefaultRunLoopMode]; / / add a Timer NSTimer * Timer = [NSTimer scheduledTimerWithTimeInterval: 2.0 target: self selector: @ the selector (test) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; / / create the listener CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler (CFAllocatorGetDefault (), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {switch (activity) {case kCFRunLoopEntry: NSLog(@"RunLoop into "); break; Case kCFRunLoopBeforeTimers: NSLog(@"RunLoop needs to handle Timers "); break; Case kCFRunLoopBeforeSources: NSLog(@"RunLoop will handle Sources "); break; Case kCFRunLoopBeforeWaiting: NSLog(@"RunLoop is going to sleep "); break; Case kCFRunLoopAfterWaiting: NSLog(@"RunLoop woke up "); break; Case kCFRunLoopExit: NSLog(@"RunLoop exits "); break; default: break; }}); // Add listener CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode) to RunLoop; // Instead of creating a RunLoop, just put the current RunLoop in the thread // 2. RunLoop [[NSRunLoop currentRunLoop] run]; //run is used to enable RunLoop CFRelease(observer); }Copy the code

Results:

2021-11-28 17:42:09.194876+0800 RunloopTest[19348:4762406] -[ViewController show] 2021-11-28 17:42:09.195422+0800 2021-11-28 17:42:09.195535+0800 RunloopTest[19348:4762406] RunLoop to handle Timers 2021-11-28 17:42:09.195626+0800 RunloopTest[19348:4762406] RunLoop to handle Sources 2021-11-28 17:42:09.195745+0800 197577+0800 RunloopTest[19348:4762406] RunLoop awake 2021-11-28 17:42:11. 197860 + 0800 RunloopTest [19348-4762406] < NSThread: 0x600001f9c600>{number = 9, Name = wyThread} 2021-11-28 17:42:11.197988+0800 RunloopTest[19348:4762406] RunLoop to handle Timers 2021-11-28 17:42:11.198094+0800 RunloopTest[19348:4762406] RunLoop to handle Sources 2021-11-28 17:42:13.200238+0800 RunloopTest[19348:4762406] RunLoop is awake 2021-11-28 17:42:13.200582+0800 RunloopTest[19348:4762406] <NSThread: 0x600001F9C600 >{number = 9, name = wyThread}...Copy the code

2.6 Relationships of RunLoop classes

Each of these objects is recognized one by one, and then the relationship between these objects

Description:

  • A RunLoop contains several modes, each of which contains several sources/timers/observers.
  • Only one Mode can be specified each time RunLoop’s main function is called, and this Mode is called CurrentMode.
  • If you need to switch Mode, you can only exit Loop and specify another Mode to enter. The main purpose of this is to separate the Source/Timer/Observer groups from each other.
  • If a mode does not have a Source/Timer/Observer, the RunLoop exits without entering the loop.

3, RunLoop and thread relationship

We already know that Runloop operates on threads, so let’s look at how it works.

There are two main ways to get runloops in daily development

CFRunLoopRef mainRunloop = CFRunLoopGetMain(); // CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();Copy the code

Next, explore with CFRunLoopGetMain()

3.1 Explore the relationship between runloops and threads

3.1.1 Access the source code of CFRunLoopGetMain

Source:

RunLoop CFRunLoopRef CFRunLoopGetMain(void) {CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // No retain needed // pthread_main_thread_np() = _CFRunLoopGet0 __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; }Copy the code

Description:

  • What is returned is a RunLoop, which is received through the CFRunLoopRef
  • What you do here is call _CFRunLoopGet0 to get the RunLoop through the main thread

3.1.2 into _CFRunLoopGet0

Source:

RunLoop CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { The default is the main thread) if (pthread_equal(t, kNilPthreadT)) {t = pthread_main_thread_np(); } __CFLock(&loopsLock); // If __CFRunLoops does not exist, there is no RunLoop if (! __CFRunLoops) { __CFUnlock(&loopsLock); // create CFMutableDictionaryRef Marked as kCFAllocatorSystemDefault CFMutableDictionaryRef dict = CFDictionaryCreateMutable (kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np())); Dict [@"pthread_main_thread_np"] = mainLoop CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // If you already have __CFRunLoops, CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); // If there is no RunLoop, create a RunLoop and store it in the dictionary. loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (! loop) { 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

Description:

  • Runloops are managed on a thread-by-thread basis. They are stored in a global runLoopDict, where threads are keys and runloops are values.
  • Create a RunLoop: The RunLoop corresponding to the main thread is created automatically when the program starts. The RunLoop corresponding to the child thread is not created when the child thread is created, but when we use the RunLoop corresponding to the child thread. In other words, the RunLoop corresponding to the child thread is created. If you don’t get a RunLoop for a child thread, its RunLoop will never be created.
  • Fetching runloops: We can fetch runloops from a runLoopDict using a specified thread.
  • RunLoop destruction: When a RunLoop is created, a callback is registered to ensure that the thread destroys its RunLoop at the same time.

3.2 Child Threads using RunLoop

Code:

- (void)viewDidLoad { [super viewDidLoad]; // The child thread runloop does not start by default, self.isStopping = NO; LGThread *thread = [[LGThread alloc] initWithBlock:^{ NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]); // Stop the current thread by controlling isStopping. The thread stops and the RunLoop ends. RunLoop end NSTimer also stopped [NSTimer scheduledTimerWithTimeInterval: 1 repeats: YES block: ^ (NSTimer * _Nonnull timer) { If (self.isStopping) {[NSThread exit];}}];  [[NSRunLoop currentRunLoop] run]; }]; thread.name = @"wy.com"; [thread start]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.isStopping = YES; }Copy the code

Running results:

2021-11-27 21:21:55.106242+0800 02-Runloop relationship [7833:4082258] <LGThread: 0x600000c44040>{number = 7, Name = WY.com}-- wy.com 2021-11-27 21:21:56.109209+0800 02- Connection between Runloop and thread [7833:4082258] Hello word 2021-11-27 21:21:57.109159+0800 02- Link between Runloop and thread [7838:4082258] Hello word 2021-11-27 21:21:58.108583+0800 [7833:4082258] hello word 2021-11-27 21:22:15.109605+0800 02-Runloop [7833:4082258] -[LGThread Dealloc]-- The thread was destroyedCopy the code

Description:

  • If we do not get a RunLoop through NSRunLoop currentRunLoop when the thread is started, the child thread does not have a RunLoop
  • A RunLoop is also not enabled directly. It is enabled through the run method
  • NSTimer is in the child thread, and we know that NSTimer is implemented based on RunLoop. If you do not start the RunLoop of the child thread, you cannot start the timer
  • When a child thread’s RunLoop is started, NSTimer is continuously executed. Print hello Word repeatedly
  • When we want to stop executing the RunLoop, we destroy the thread, the thread destroys the RunLoop, and the NSTimer stops executing.

4. The execution of Runloop

4.1 Event Adding Process

Add time source as an example source code:

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) {// If (modeName == kCFRunLoopCommonModes) {// If (modeName == kCFRunLoopCommonModes) { CFSetRef set = rl->_commonModes? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; if (NULL == rl->_commonModeItems) { rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); } // add RLT to RunLoop's _commonModeItems CFSetAddValue(rl->_commonModeItems, RLT); if (NULL ! = set) { CFTypeRef context[2] = {rl, rlt}; / * the add new item to all common modes * / / / add a new item in the set of all the set CFSetApplyFunction (set, (__CFRunLoopAddItemToCommonModes). (void *)context); CFRelease(set); Cfrunloopfindmode (rl, modeName, true); cfrunloopFindMode (rl, modeName, true); // If NSTimer is set for the first time, _timers if (NULL! = rlm) { if (NULL == rlm->_timers) { CFArrayCallBacks cb = kCFTypeArrayCallBacks; cb.equal = NULL; rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); }} // Check whether matches if (NULL! = rlm && ! CFSetContainsValue(rlt->_rlModes, rlm->_name)) { __CFRunLoopTimerLock(rlt); if (NULL == rlt->_runLoop) { rlt->_runLoop = rl; } else if (rl ! = rlt->_runLoop) { __CFRunLoopTimerUnlock(rlt); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); return; } // Add mode to Timer CFSetAddValue(RLT ->_rlModes, RLM ->_name); __CFRunLoopTimerUnlock(rlt); __CFRunLoopTimerFireTSRLock(); __CFRepositionTimerInMode(rlm, rlt, false); __CFRunLoopTimerFireTSRUnlock(); if (! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) { // Normally we don't do this on behalf of clients, but for // backwards compatibility due to the change in timer handling... if (rl ! = CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); } } if (NULL ! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code

Description:

  • You can see that there are two modes, one added to kCFRunLoopCommonModes and one non-KCFrUnLoopCommonModes
  • The first case needs to be added to the _commonModeItems of the RunLoop, so it is called a common Item
  • In the second case, create an NSTimer object, set the RunLoop to the current RunLoop, and set the mode to NSTimer

4.2 Event Execution Process

The upper layer is executed by the run method, so let’s print it out

4.2.1 Viewing stack Information

The timer

GCD main queue:

Note: Child threads do not have runloops by default, but they can be added manually

block

Incident response

Implement listening Observer

4.2.2 All response types of RunLoop

As you can see above, some events are responded to by RunLoop at the bottom. So what are the responses of RunLoop?

Check it out in the source code

Static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline)); static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) { _dispatch_main_queue_callback_4CF(msg); asm __volatile__(""); Balktail-call optimization} //Observer listener response static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { if (func) { func(observer, activity, info); } asm __volatile__(""); Balkd-tail-call optimization} static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__()  __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) { if (func) { func(timer, info); } asm __volatile__(""); Balktail call optimization} //block response static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) { if (block) { block(); } asm __volatile__(""); Balktail-call optimization} static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) { if (perform) { perform(info); } asm __volatile__(""); Balktail call optimization} static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__( #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info), mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply, #else void (*perform)(void *), #endif void *info) { if (perform) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI *reply = perform(msg, size, kCFAllocatorSystemDefault, info); #else perform(info); #endif } asm __volatile__(""); // thwart tail-call optimization }Copy the code

conclusion

  • We can see that there are six response types
  • The process is the same for each response, but the final execution type is different
  • At the bottom, it’s actually implemented through the CoreFoundation framework
  • The execution process is CFRunLoopRunSpecific->__CFRunLoopRun->__CFRunLoopDoXXX-> the last execution
  • The next step is to look at the execution process in this order at the bottom
  • The top layer executes the run method and the bottom layer is actually CFRunLoopRun()

CFRunLoopRun()

The top layer executes the run method and the bottom layer is actually CFRunLoopRun()

The source code

// Start runLoop, do... While loop 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

Description:

  • It is obvious that the execution of RunLoop is a do… A while loop is just a waiting loop

4.2.4 CFRunLoopRunSpecific ()

Source:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); CurrentMode = __CFRunLoopFindMode(rl, modeName, false); 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; Observers: Observers are about to enter loop. if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); //2. Run the RunLoop. Began to run laps result = __CFRunLoopRun (rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // Notify Observers: RunLoop is about to exit. if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; }Copy the code

Description:

  • There are three cases, the first is to start a RunLoop, the second is to respond to some event source, and the third is to end the RunLoop
  • Both starting RunLoop and ending RunLoop are done by listening on the Observer
  • The mode of a RunLoop is queried by the mode name, which is then assigned to the current RunLoop

4.2.5 __CFRunLoopRun ()

This function has a lot of code, and we only need to look at the core, so just pick out the core code

Source:

/* rl, RLM are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef RLM, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){start a timer by GCD, Dispatch_source_t timeout_timer = NULL; . dispatch_resume(timeout_timer); int32_t retVal = 0; // Notify Observers: about to deal with timer events __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers); / / notify Observers: __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeSources) // Handle Blocks __CFRunLoopDoBlocks(rl, RLM); Sources0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, RLM, stopAfterHandle); If (sourceHandledThisLoop) {// Process Blocks __CFRunLoopDoBlocks(rl, RLM); } / / judge whether port (Source1) if (__CFRunLoopWaitForMultipleObjects (NULL, & dispatchPort, 0, 0, & livePort, NULL)) {// Handle messages 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); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); // notify Observers: astounded, ending dormancy __CFRunLoopDoObservers(rl, RLM, kCFRunLoopAfterWaiting); Handle_msg: if (awakened by timer) {// Handle Timers __CFRunLoopDoTimers(rl, RLM, mach_absolute_time()); }else if (GCD wake up){// handle GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); }else if (by source1){// 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

Description:

  • Here by do… The while loop loops through all the event sources and you can see the entire loop
  • do… A while loop can keep running
  • CPU resources can be saved by sleeping and waking up, and sleep begins after this round of message processing
  • Wait until woken up by events such as NSTimer, GCD, source1 and so on to continue processing events
  • If it is Source1, it will go to handle_msg to wake up
  • When the time of NSTimer is up, it will automatically wake up and go to handle_msg to wake up

4.2.6 Block execution is used as an example

Source:

static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked ... CFSetRef commonModes = rl->_commonModes; CFStringRef curMode = rlm->_name; struct _block_item *prev = NULL; struct _block_item *item = head; while (item) { struct _block_item *curr = item; item = item->_next; Boolean doit = false; If (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) { Block's mode == commonMode, And block mode added to the public in the mode of doit = CFEqual (curr - > _mode, curMode) | | (CFEqual (curr - > _mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode)); } else { 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; CFRelease(curr->_mode); free(curr); If (doit) {// start executing block __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); did = true; } Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc } } ... return did; }Copy the code

Description:

  • Rl is the RunLoop of the current thread, RLM is the mode of the current RunLoop, and item is the modeItem from the RunLoop
  • There are two conditions to determine the execution. The first is whether the mode in which the event source is located is the current mode. If so, the execution can be performed
  • Second, whether the mode in which the event source is located is a Commonmode, and the current mode is a Commonmode. If so, the event can be executed
  • If doit is YES, __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block) is executed; . This is consistent with the stack information we looked at above

4.2.7 summary

  • Yellow: notifies the Observer of phases;
  • Blue: logic for processing messages;
  • Green: branching judgment logic;

  • We add an event to the RunLoop in two ways
    • First: Add to CommonModes. In this case, events are added to CommonModesItems as common events, corresponding to any mode marked common.
    • Second: Add to non-commonModes. The mode is retrieved, the time source is added to the _timers array in the mode, and the corresponding mode and RunLoop are set in the time source object __CFRunLoopTimer.
  • When RunLoop is executed, all event sources are in do… In the while loop
  • When we run and start RunLoop, we start RunLoop first, we stay in the loop for the rest of the time, process the event, sleep when it’s done, wake up when it’s done, go to sleep again, and so on
  • The Observer listens to us as we start, close, and process events
  • There are three types of event processing: Source0, Source1 and Timer
  • Source0 does not wake up actively. Timer and Source1, as well as the main queue, can wake up

5. Common use

5.1 Resident Procedure

When we create a child thread, it is destroyed when the child thread completes its task. This is because the child thread has no RunLoop by default. To keep the child thread running, you can add a RunLoop and start it, which makes it a resident thread.

5.2 usage of NSTimer

  • NSTimer is based on RunLoop, which has multiple modes of operation
  • Different modes have different response events in different situations
  • When you swipe, you switch from the default mode to the slide mode, and NSTimer is added to the default mode in the main thread by default, so it doesn’t respond to NSTimer when you swipe mode
  • We can put it in CommonModes because the sliding modes and default modes provided by the system are both CommonModes by default, and when we put an event in CommonModeItems, any mode marked Common will respond to that event

CADisplayLink is the kernel calculation time, independent of RunLoop, so it is on time

5.3 AutoreleasePool

  • The auto-release pool is dependent on the thread’s RunLoop, which creates a pool when the RunLoop starts and releases the pool when the RunLoop sleeps
  • RunLoop has observers to listen on, and when the pool is about to be started, the thread pool is opened, with the highest priority, because the pool is likely to be used for other transactions
  • The thread pool is destroyed when the listener detects that the pool is about to be destroyed, and the thread pool is destroyed with the lowest priority, and the garbage is collected last after other transactions are completed.

5.4 Event Response, Gesture recognition, and interface refresh

Application-level event responses are implemented in Source1, and events are executed when RunLoop is awake. The system-level response is Source0, and other threads send MSG through the port to that thread, which wakes up the thread’s Runloop

5.5. The main thread is used to wake up via GCD

As you can see in the source code, execution of the main thread wakes up the RunLoop, and the main queue is started based on the RunLoop

5.6 Block execution is runloop-based

In the source code, you can see that Runloop actively executes blocks during execution