Runloop

1. An overview of the

In general, a thread can only perform one task and then exit, so if we needed a mechanism for a thread to handle time at any time without exiting, RunLoop would be one of those mechanisms. Runloop is an implementation of the event receiving and distribution mechanism.

A RunLoop is actually an object that is used within the loop to handle various events (such as touch events, UI refresh events, timer events, Selector events) that occur while the program is running to keep it running. And when there is no event processing, it will enter the sleep mode, so as to save CPU resources and improve program performance.

Simply put, run loop is a large event-driven loop, as shown in the following code:

Int main(int argc, char * argv[]) {while (AppIsRunning) {whoWakesMe = SleepForWakingUp(); Event = GetEvent(whoWakesMe); // Start handling the event HandleEvent(event); } return 0; }Copy the code

2. Basic functions of Runloop

2.1 Keep the program running continuously

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. Not only iOS, but also other programming platforms, such as Android and Windows, have a mechanism similar to Runloop to ensure the continuous running of programs.

2.2 Handling various events in the App

System level

GCD, mach kernel, block, pthread

The application layer

NSTimer, UIEvent, Autorelease, NSObject(NSDelayedPerforming), NSObject(NSThreadPerformAddition), CADisplayLink, CATransition, CAAnimation, dispatch_get_main_queue() (blocks dispatched to main queue in GCD are dispatched to main Runloop), NSPort, NSURLConnection, AFNetworking (this third-party network request framework uses to add itself to Runloop listening events when starting a new thread)

2.3 Save CPU resources and improve program performance

When the program is running and nothing is done, the Runloop tells the CPU that there is nothing to do, I need to rest, and the CPU frees up resources to do something else. When something is done, the Runloop gets up and does something else.

3. Enable Runloop

Program entrance

The entry point for iOS applications is the main function

int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

When the main thread is opened, a Runloop corresponding to the main thread is run. The Runloop must be opened in the main function at the entrance of the program.

Position in the main thread stack

At the bottom of the stack is start(dyld), and then main, UIApplication(main.m) -> GSEventRunModal(Graphic Services) -> RunLoop(including CFRunLoopRunSpecific, __CFRunLoopRun, __CFRunLoopDoSouces0, CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION) -> Handle Touch Event

4. The Runloop principle

CFRunLoop open source: http://opensource.apple.com/source/CF/CF-855.17/

Runloop source code:

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

We find that RunLoop is indeed implemented by doing while by judging the value of result. Therefore, we can think of RunLoop as an infinite loop. Without RunLoop, the UIApplicationMain function will return directly after execution, and the program will not run continuously.

Pseudo-code for execution order:

Int32_t __CFRunLoopRun() {// Notify that runloop __CFRunLoopDoObservers(KCFRunLoopEntry) is about to enter; Do {// notify that timer and source __CFRunLoopDoObservers(kCFRunLoopBeforeTimers) will be handled; __CFRunLoopDoObservers(kCFRunLoopBeforeSources); // Handle the non-delayed main thread call __CFRunLoopDoBlocks(); // Handle the Source0 event __CFRunLoopDoSource0(); if (sourceHandledThisLoop) { __CFRunLoopDoBlocks(); } /// If a Source1 (port-based) is ready, process the Source1 directly and jump to the message. if (__Source0DidDispatchPortLastTime) { Boolean hasMsg = __CFRunLoopServiceMachPort(); if (hasMsg) goto handle_msg; } /// Notify Observers: threads of RunLoop are about to enter sleep. if (! sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); } // GCD dispatch main queue CheckIfExistMessagesInMainDispatchQueue(); __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); / / wait for the kernel mach_msg event mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts (); // Wait for... // Wake up from waiting __CFRunLoopDoObservers(kCFRunLoopAfterWaiting); If (wakeUpPort == timerPort) __CFRunLoopDoTimers(); // Handle asynchronous method awakenings such as dispatch_async else if (wakeUpPort == mainDispatchQueuePort) __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() else __CFRunLoopDoSource1(); // Again make sure that there are no synchronized methods that need to call __CFRunLoopDoBlocks(); } while (! stop && ! timeout); Runloop __CFRunLoopDoObservers(CFRunLoopExit); }Copy the code

5. The Runloop object

RunLoop objects include NSRunLoop objects in Fundation and CFRunLoopRef objects in CoreFoundation. Since the Fundation framework is based on CFRunLoopRef encapsulation, we will study the CFRunLoopRef source code for RunLoop.

Get the Runloop object

//Foundation [NSRunLoop currentRunLoop]; // Get the current thread's RunLoop object [NSRunLoop mainRunLoop]; //Core Foundation CFRunLoopGetCurrent(); // Get the current thread's RunLoop object CFRunLoopGetMain(); // Get the main thread RunLoop objectCopy the code

[NSRunLoop currentRunLoop] returns the current thread’s runloop if it already exists, or creates a new runloop object if it does not.

6. Runloops and threads

6.1 Relationship between Runloops and threads

  1. Each thread has a unique Runloop object corresponding to it
  2. The Runloop for the main thread is created automatically, and the Runloop for the child thread needs to be created manually
  3. The Runloop is created on the first fetch and destroyed at the end of the thread
  4. Thread contains a CFRunloop, a CFRunloop contains a CFRunloopMode, and model contains CFRunloopSource, CFRunloopTimer, and CFRunloopObserver.

6.2 Create Runloop to be associated with the main thread

CFRunloopRef source

/ / create a dictionary CFMutableDictionaryRef dict = CFDictionaryCreateMutable (kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np())); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);Copy the code

6.3 Creating runloops that you want to associate with child threads

Apple does not allow the creation of runloops directly, and only provides two auto-fetching functions: CFRunLoopGetMain() and CFRunLoopGetCurrent(). CFRunLoopRef source code:

Void CFRunLoopRun(void) {CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); } // start with the specified Mode, RunLoop timeout allowed int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } int CFRunLoopRunSpecific(RunLoop, modeName, seconds, CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); // If there is no source/timer/observer in mode, return it directly. if (__CFRunLoopModeIsEmpty(currentMode)) return; /// 1. Notify Observers: RunLoop is about to enter loop. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); // the internal function, Loop __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {Boolean sourceHandledThisLoop = NO; int retVal = 0; Do {/// 2. Notify Observers: RunLoop is about to trigger a Timer callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); /// 3. notify Observers: RunLoop is about to trigger the Source0 (non-port) callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); // Execute the added block __CFRunLoopDoBlocks(runloop, currentMode); /// 4. RunLoop triggers the Source0 (non-port) callback. sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); // Execute the added block __CFRunLoopDoBlocks(runloop, currentMode); /// 5. If there is a port based Source1 in ready state, process the Source1 directly and jump to the message. if (__Source0DidDispatchPortLastTime) { Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) if (hasMsg) goto handle_msg; } /// Notify Observers: threads of RunLoop are about to enter sleep. if (! sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); } /// 7. Call mach_msg and wait for the message to accept mach_port. The thread will sleep until it is awakened by one of the following events. /// • A port-based Source event. __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg } /// 8. Notify Observers: The threads of RunLoop have just been awakened. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); // the message is received and processed. Handle_msg: /// 9.1 If a Timer runs out, trigger the Timer's callback. if (msg_is_timer) { __CFRunLoopDoTimers(runloop, currentMode, Mach_absolute_time ())} // 9.2 If there are blocks dispatched to main_queue, execute the block. else if (msg_is_dispatch) { __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } /// 9.3 If a Source1 (port based) emits an event, To deal with this event else {CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort (runloop currentMode, livePort); sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); if (sourceHandledThisLoop) { mach_msg(reply, MACH_SEND_MSG, reply); } // execute the block __CFRunLoopDoBlocks(runloop, currentMode) added to the Loop; If (sourceHandledThisLoop && stopAfterHandle) {/// return when the loop is finished. retVal = kCFRunLoopRunHandledSource; RetVal = kCFRunLoopRunTimedOut;} else if (timeout) {retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(runloop)) {retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(runloop, RetVal = kCFRunLoopRunFinished; } // If there is no timeout, mode is not available and loop is not stopped, continue loop. } while (retVal == 0); } /// 10. Notify Observers: RunLoop is about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); }Copy the code

As you can see, there is a one-to-one correspondence between threads and runloops, and the relationship is stored in a global Dictionary. There is no RunLoop when the thread is created, and if you don’t grab it, it never will. RunLoop creation occurs at the first fetch and RunLoop destruction occurs at the end of the thread. You can only get runloops inside a thread (except for the main thread).

[NSRunLoop currentRunLoop]; Method is called to see if there is a RunLoop in the dictionary for the child thread. If there is a RunLoop, it returns a RunLoop. If there is no RunLoop, it creates one and stores its child thread to the dictionary.

7. Runloop-related classes

Core Foundation classes for RunLoop

CFRunLoopRef // Get the current RunLoop and the primary RunLoop CFRunLoopModeRef // Only one mode can be selected and different operations can be performed in different modes CFRunLoopSourceRef // Event source, Input source CFRunLoopTimerRef // Timer time CFRunLoopObserverRef // ObserverCopy the code

7.1 CFRunLoopModeRef

A Runloop contains several modes, each of which contains several Source/Timer/Observer. Each time the main function of Runloop is called, only one of the modes can be specified. 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.

The system registers five modes by default, of which the first and second are common:

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, start will no longer use 4. After the completion of GSEventReceiveRunLoopMode: accept system internal Mode of events, usually in less than 5. KCFRunLoopCommonModes: This is a placeholder Mode, used to mark kCFRunLoopDefaultMode and UITrackingRunLoopMode, not a real ModeCopy the code

The Source/Timer/Observer above is collectively referred to as a Model item, and an item can be added to multiple modes at the same time. However, it does not work if an item is repeatedly added to the same mode. If there is no item in a mode, the Runloop exits without entering the loop.

When we use NSTimer to perform something every time we slide UIScrollView, NSTimer will pause. When we stop sliding, NSTimer will resume again. Let’s look at the following code:

-(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

As can be seen from the above code, NSTimer does not work because of Mode switch, because if we use the timer on the main thread, the Mode of RunLoop is kCFRunLoopDefaultMode, that is, the timer belongs to kCFRunLoopDefaultMode. So when we slide the ScrollView, RunLoopMode will switch to UITrackingRunLoopMode, so the timer on the main thread will no longer work, the method called will no longer execute, and when we stop sliding, Switch the Mode of RunLoop back to kCFRunLoopDefaultMode, and all nstimers work again.

Timers can also be created using GCD, and are much more precise:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {// create a dispatch_queue_t queue = dispatch_get_global_queue(0, 0); Dispatch_source_t timer = dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); Self. timer = timer; self.timer = timer; self.timer = timer; //2. Set timer start time, interval time, precision /* 1st parameter: which timer to set 2nd parameter: start time 3rd parameter: interval time 4th parameter: precision is generally 0. Increasing error within the allowed range can improve the performance of the program *NSEC_PER_SEC */ dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 *NSEC_PER_SEC, 0 *NSEC_PER_SEC); Dispatch_source_set_event_handler (timer, ^{NSLog(@"-- --%@--",[NSThread currentThread]); }); / / start dispatch_resume (timer); }Copy the code

7.2 CFRunLoopSourceRef

There are two types of Source:

Source0: non-port-based events that are actively triggered by the user (click button or click screen) Source1: Port-based events that send messages to each other through the kernel and other threads (kernel dependent) Note: Source1 dispatches operations to Source0 during processing

7.3 CFRunLoopTimer

NSTimer is a wrapper around RunLoopTimer

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

Copy the code

7.4 CFRunLoopObserverRef

CFRunLoopObserverRef is the observer that listens for state changes in the RunLoop. Let’s go straight to the code and add a listener to the RunLoop to listen for its running status:

-(void)touchesBegan:(NSSet<UITouch *> *) Touches withEvent:(UIEvent *) Event { CFAllocatorGetDefault() assigns the second parameter CFOptionFlags Activities by default: > > < span class = > cfrunloopAllActivities > < span class = > < span class = > CFIndex order > < span class = > Typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0), // About to enter RunLoop kCFRunLoopBeforeTimers = (1UL << 1), KCFRunLoopBeforeSources = (1UL << 2), Source kCFRunLoopBeforeWaiting = (1UL << 5), KCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopExit = (1UL << 7), and RunLoop is about to exit kCFRunLoopAllActivities = 0x0FFFFFFFU }; */ 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 rest "); break; Case kCFRunLoopAfterWaiting: NSLog(@"RunLoop wakes up "); break; Case kCFRunLoopExit: NSLog(@"RunLoop exits "); break; default: break; }}); // Add listener to RunLoop /* CFRunLoopRef rl: The second parameter, CFRunLoopObserverRef Observer, and the third parameter, CFStringRef mode, listen for the status of the RunLoop in which mode */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); /* Core Foundation (CF) {Create, Copy, Retain, etc.); release GCD; */ CFRelease(Observer); */ CFRelease(Observer); }Copy the code

Running results:

8. Runloop exit

  1. Main thread enchantment Runloop exits
  2. There are timers, sources, and observers in the Mode. These ensure that the Runloop is not idling and running when the Mode is not empty. When the Mode is empty, the Runloop exits immediately.
  3. When we start Runloop, we can set when to stop it.
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#> [NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#>  beforeDate:<#(nonnull NSDate *)#>Copy the code

9. Some Runloop questions

9.1 Under what circumstances can nstimer-based unicast be paused by page scrolling, how can it not be paused, and why?

NSTimer doesn’t work because of the Mode switch, because if we use a timer on the main thread, the Mode of RunLoop is kCFRunLoopDefaultMode, that is, the timer is kCFRunLoopDefaultMode, So when we slide the ScrollView, RunLoopMode will switch to UITrackingRunLoopMode, so the timer on the main thread will no longer work, the method called will no longer execute, and when we stop sliding, Switch the Mode of RunLoop back to kCFRunLoopDefaultMode, and all nstimers work again. If the timer is to continue, you need to register NSTimer as kCFRunLoopCommonModes.

9.2 How is the performSelecter-related method executed? Is it the same in child threads?

When calling NSObject performSelecter: afterDelay: after its internal will actually create a Timer and add to the current thread RunLoop. So if the current thread does not have a RunLoop, this method will fail. When the performSelector: onThread: when, in fact, it will create a Timer is added to the corresponding thread, in the same way, if the corresponding thread no RunLoop this method will fail.

9.3 Are event response and gesture recognition processing consistent? Why?

Incident response: apple registered a Source1 (based on the Mach port) to the receiving system, the callback function as __IOHIDEventSystemClientQueueCallback (). When a hardware event (touch/lock/shake, etc.) occurs, an IOHIDEvent event is first generated by IOKit. Framework and received by SpringBoard. SpringBoard only receives events such as buttons (lock screen/mute etc.), touch, acceleration and proximity sensor, and then forwards them to the App process using Mach port. Then apple registered the Source1 will trigger the callback, and call the _UIApplicationHandleEventQueue () distribution within the application. _UIApplicationHandleEventQueue () will wrap IOHIDEvent process, and as a UIEvent processing or distribution, including identifying UIGesture/processing screen rotation/send UIWindow, etc. Usually click event such as a UIButton, touchesBegin/Move/End/Cancel events are completed in the callback.

Gesture recognition: when the above _UIApplicationHandleEventQueue () to identify a gesture, the first call will Cancel the current touchesBegin/Move/End series callback to interrupt. The system then marks the corresponding UIGestureRecognizer as pending. Apple registered a Observer monitoring BeforeWaiting (Loop entering hibernation) events, the callback function Observer is _UIGestureRecognizerUpdateObserver (), Internally it gets all GestureRecognizer that has just been marked for processing and executes the GestureRecognizer callback. This callback is handled when there are changes to the UIGestureRecognizer (create/destroy/state change).

9.4 When and Why is the interface refreshed in a timely manner?

When in operation the UI, such as changing the Frame, update the UIView/CALayer level, or manually invoked the UIView/CALayer setNeedsLayout/setNeedsDisplay method, The UIView/CALayer is marked to be processed and submitted to a global container.

Apple registers an Observer to listen for BeforeWaiting(about to go to sleep) and Exit (about to Exit Loop) events, calling back to perform a long function: _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (). This function iterates through all the UIViews/Calayers to be processed to perform the actual drawing and adjustment, and update the UI. So the interface refresh doesn’t have to happen immediately after the setNeedsLayout related code is executed.

9.5 When did the automatic release pool creation and destruction occur during project execution?

Autoreleasepool {} implicitly creates an autoreleasepool for each runloop, and all autoreleasepool autoreleasepool stacks. At the end of each runloop, the autoreleasePool at the top of the stack is destroyed and a release is made for each object. In particular, When you use the block version of the container’s enumerator, an autoreleasePool is automatically added

[array enumerateObjectsUsingBlock: ^ (id obj, NSUInteger independence idx, BOOL * stop) {/ / here is surrounded by a local @ autoreleasepool}].Copy the code

9.6 When we need to execute proxy methods or callbacks on child threads, how can we ensure that the current thread is not destroyed?

Event_loop: A thread exits after completing a task. To ensure that the thread does not exit, you can use the following methods:

function do_loop() { initialize(); do { var message = get_next_message(); process_message(message); } while (message ! = quit); }Copy the code

The Event_loop model starts a loop that keeps the thread from exiting. This is a model used in many operating systems, such as RunLoop in OS/iOS. The most important role of this model is to manage events/messages, wake up processing as soon as a new message arrives, and sleep when there are no pending messages to avoid resource waste.

10 Runloop use

10.1 AFNetworking

If you use the NSOperation+NSURLConnection concurrency model, you will face the problem that the NSOperation object does not receive callbacks because the thread exits before the NSURLConnection download is complete. AFNetWorking solves this problem by calling the delegate method of NSURLConnection in the thread runloop initiated by the connection as written on the official GUID NSURLConnection. Therefore, AFNetWorking directly borrowed the implementation method of Apple’s own Demo to create a single global thread and a built-in Runloop. All connections are initiated by this runloop and callback is received by it, which does not occupy the main thread or consume CPU resources.

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
     @autoreleasepool {
          [[NSThread currentThread] setName:@"AFNetworking"];
          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          [runLoop run];
     }
}

+ (NSThread *)networkRequestThread {
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
          _networkRequestThread =
          [[NSThread alloc] initWithTarget:self
               selector:@selector(networkRequestThreadEntryPoint:)
               object:nil];
          [_networkRequestThread start];
     });

     return _networkRequestThread;
}
Copy the code

Similarly, you can use this method to create a thread of resident service.

10.2 TableView to achieve smooth scrolling delay loading pictures

Using the feature of CFRunLoopMode, the loading of pictures can be put into the mode of NSDefaultRunLoopMode, so that the mode of UITrackingRunLoopMode will not be affected by the loading.

UIImage *downloadedImage = ... ; [self.imageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];Copy the code

10.3 Handle autonomously when receiving the signal of program crash, such as pop-up prompt

CFRunLoopRef runLoop = CFRunLoopGetCurrent(); NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop)); While (1) {for (NSString *mode in allModes) {CFRunLoopRunInMode((CFStringRef)mode, 0.001, false); }}Copy the code

10.4 Asynchronous Test

- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout { __block Boolean fulfilled = NO; void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { fulfilled = block(); if (fulfilled) { CFRunLoopStop(CFRunLoopGetCurrent()); }}; CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting); CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); // Run! CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false); CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); CFRelease(observer); return fulfilled; }Copy the code