Run Loops is part of the basic infrastructure associated with Threads. A Run loop is an Event processing loop that can be used to plan work and coordinate the receipt of received events. The purpose of the Run loop is to keep the Thread busy when there is work to do, and to put the Thread to sleep when there is no work to do. (The official explanation was too awkward at the first glance, but we can still catch some key points. Originally, our thread would be released and destroyed after completing the task, but with the help of Run Loop, the thread will no longer take the initiative to destroy itself, but is in standby state waiting for us to give it another task. In other words, run loop keeps our thread alive. Let’s try to understand the concept of run loop.

Runloop concept

Generally speaking, a thread can only execute one task at a time, and when it is finished, the thread exits. If we need a mechanism that allows threads to handle events at any time without exiting, this model is often referred to as an Event Loop. Event Loop is implemented in many systems and frameworks, such as Node.js Event processing, Windows program message Loop, and OSX/iOS Run Loop. The key to implementing this model is message-based: manage events/messages, let threads sleep when there is no message to avoid resource usage, and wake up as soon as a message arrives to perform tasks.

What is run loop? As the name suggests, a “run loop” is a “run loop”. The core code of the “run loop” is a stateful do while loop. Each loop is equivalent to a complete loop, and the thread processes the events generated in the current loop. Of course, what we normally write in a function is a loop that explicitly ends, and the contents of the loop are written at the beginning. We can’t dynamically change or insert the contents of the loop, unlike a run loop. The Run loop executes the do while loop as long as there is no timeout or deliberate exit, so the thread is guaranteed not to exit and we can add tasks to the thread as needed.

So why do threads have run loops? In fact, our APP can be understood as event-driven (including iOS and Android apps). Our touch screens, network callbacks, etc., are all events, i.e., events. These events will be distributed to our APP, and the APP will be distributed to the corresponding thread when it receives the event. Normally, if a thread does not have a Run loop, then a thread can only execute one task at a time, and the thread will exit when the execution is complete. In order for the APP thread to be able to handle events or wait for events (such as asynchronous events), we need to keep the thread alive, that is, we can’t let the thread exit early. In this case, the run loop is useful. In fact, it is not necessary to specify a Run loop for the thread. Then you need to bind a Run loop to the thread. That is, a Run loop ensures that the thread can always handle events. A core runloop source code analysis

In general, events are not generated indefinitely, so there is no need for the thread to run indefinitely. The Run loop can go to sleep when there is no event processing to avoid the endless do while running. Notice here that both threads and run loops can go to sleep. To get a sense of the concept, let’s look at some code that represents the status of the run loop:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // Enter the Run Loop.
    kCFRunLoopBeforeTimers = (1UL << 1), // Run Loop is about to start processing Timer
    kCFRunLoopBeforeSources = (1UL << 2), // Run Loop is about to start processing Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // Run Loop is about to go to sleep
    kCFRunLoopAfterWaiting = (1UL << 6), // Run Loop wakes up from sleep
    kCFRunLoopExit = (1UL << 7), // RunLoop exit (corresponding to kCFRunLoop Entry)
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

The relationship between run loop and thread: one thread corresponds to one run loop. The main run loop of the main thread is started by default, so our program will not exit. The run loop of the child thread is started on demand (calling the run method). The Run Loop is the thread’s event manager, or event manager for the thread, which manages the events to be processed by the thread in order, deciding which events are submitted to the thread for processing and when.

How does a run loop switch from one state to another? How does a run loop go to sleep and wake up? And how does it communicate with the thread? How do I keep threads alive? The information collected shows that run Loop is based on the kernel and Mach port, which involves too profound. Here we temporarily study the upper layer, and then we will learn its bottom content after we understand the basic application and some source code implementation. β›½ ️ β›½ ️

How does run loop interact with threads to keep them alive?

Main run loop Starts

The main queue of the main thread is created by default when the app starts. The main run loop of the main thread is created and started by default when the app starts. Creating an OC Single View App using Xcode automatically generates the following main.m file:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        NSLog(@"πŸƒ πŸƒ ‍ ♀ ️ πŸƒ πŸƒ ‍ ♀ ️..."); // Insert a print statement here
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    // return 0;
    
    UIApplicationMain(argc, argv, nil, appDelegateClassName); The statement is broken down as follows:
    // int result = UIApplicationMain(argc, argv, nil, appDelegateClassName);
    // return result; // ⬅️ make a breakpoint on this line. The executor will find that the breakpoint is invalid because main never executes there
}
Copy the code

The last line of the return statement from main returns UIApplicationMain. We comment out this line and add a return 0; And you’ll see that when you run the program you’re done with the NSLog statement and you go straight back to the phone desktop, and the last line is return UIApplicationMain(argc, argv, nil, appDelegateClassName); So if we run our application and we get to the home page of our app and we don’t end our application, then we kind of figured out that UIApplicationMain doesn’t return, it doesn’t return, so main doesn’t return, main doesn’t return, Therefore, our app will not automatically stop running and return to the desktop (of course, the function here will not return is different from what we see when the thread is blocked or even deadlocked function does not return). Look at the declaration of UIApplicationMain. See that it is a function that returns an int.

UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nullable argv[_Nonnull], NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
Copy the code

UIApplicationMain Creates the Application object and the application delegate and sets up the event cycle. Return Value Even though an INTEGER return type is specified, this function never returns. When users exits an iOS app by pressing the Home button, The application moves to the background. This function never returns even if an integer return type is specified. When the user exits an iOS app by pressing the Home button, the app moves to the background. The Discussion… It also sets up the main event loop, including the application’s run loop, and begins processing events…. Despite the declared return type, this function never returns…. It also sets the main Event loop, including the application’s run loop (main Run loop), and starts processing events. . Although the return type is declared, this function never returns.

Looking at UIApplicationMain in the developer documentation, the summary tells us that the UIApplicationMain function does: Create the application object and the application proxy, set the Event Cycle, see the Return Value, Apple has explicitly told us that UIApplicationMain does not Return, And we are also told in Discussion that the UIApplicationMain function starts the main Run loop and starts processing events for us.

Main is the entry point for our application, and then WE call UIApplicationMain which inside of it opens the main run loop for us, and when we try to understand why our application doesn’t exit, Does it mean that our application is wrapped in the do while loop of the main Run loop from the start?

UIApplicationMain (runloop); UIApplicationMain (runloop);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int retVal = 0;
        do {
            // Waiting for news while asleep
            int message = sleep_and_wait(a);// Process the message
            retVal = process_message(message);
        } while (retVal == 0);
        return 0; }}Copy the code

Add a symbol breakpoint for CFRunLoopGetMain to run the program, and then print the thread stack on the console using bt command. See UIApplicationMain starts main Run loop.

(lldb) bt 
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 // ⬅️ 'com.apple.main-thread' is currently our main thread
  * frame #0: 0x00000001de70a26c CoreFoundation`CFRunLoopGetMain // ⬅️ CFRunLoopGetMain Obtains the run loop of the main thread
    frame #1: 0x000000020af6d864 UIKitCore`UIApplicationInitialize + 84 
    frame #2: 0x000000020af6ce30 UIKitCore`_UIApplicationMainPreparations + 416
    frame #3: 0x000000020af6cc04 UIKitCore`UIApplicationMain + 160 ⬅️ UIApplicationMain function
    frame #4: 0x00000001008ba1ac Simple_iOS`main(argc=1, argv=0x000000016f54b8e8) at main.m:20:12 // ⬅️ main
    frame #5: 0x00000001de1ce8e0 libdyld.dylib`start + 4 // ⬅️ load dyld and dynamic library
(lldb) 
Copy the code

How to resuscitate the child thread — manually start the thread’s run loop

First of all, “Generally speaking, a thread can only execute one task at a time, and when it is finished, the thread will exit. This is the proof. Here we use nsThreads as thread objects. We first create a CommonThread class that inherits from nsThreads, and then override its dealloc function. This is because the creation and destruction of NSThread objects inside app affects our observation.

/ / CommonThread definition

// CommonThread.h 
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CommonThread : NSThread
@end
NS_ASSUME_NONNULL_END

// CommonThread.m
#import "CommonThread.h"
@implementation CommonThread
- (void)dealloc {
    NSLog(@"πŸ€ πŸ€ πŸ€ % @ CommonThread % s", self, __func__);
}
@end
Copy the code

Then we write the following test code in the viewDidLoad function of the root controller:

NSLog(@"πŸ”ž START: % @", [NSThread currentThread]);
{
    CommonThread *commonThread = [[CommonThread alloc] initWithBlock:^{
        NSLog(@"πŸƒ ‍ ♀ ️ πŸƒ ‍ ♀ ️ % @", [NSThread currentThread]);
    }];
    [commonThread start];
}
NSLog(@"πŸ”ž END: % @", [NSThread currentThread]);

// Console print:
πŸ”ž START: <NSThread: 0x282801a40>{number = 1, name = main}
πŸ”ž END: <NSThread: 0x282801a40>{number = 1, name = main}
πŸƒβ€β™€οΈπŸƒβ€β™€οΈ <CommonThread: 0x2825b6e00>{number = 5, name = (null)} / / the child threadπŸ€ πŸ€ πŸ€ < CommonThread:0x2825b6e00>{number = 5, name = (null)} CommonThread -[CommonThread dealloc] // commonThread thread object destroyed (thread exit)
Copy the code

According to the console print, we can see that when the task in the commonThread completes, the commonThread is released and destroyed (thread exits). So let’s try to use run loop to keep commonThread from exiting, and to observe the exit of run loop (NSRunLoop object destruction), We added a class of NSRunLoop and overwrote the dealloc function in the class. So we don’t have to worry about NSRunLoop objects inside our app. So let’s add the run loop fetch and run to the thread based on the above code.

NSLog(@"πŸ”ž START: % @", [NSThread currentThread]);
{
    CommonThread *commonThread = [[CommonThread alloc] initWithBlock:^{
        NSLog(@"πŸƒ ‍ ♀ ️ πŸƒ ‍ ♀ ️ % @", [NSThread currentThread]);
        
        // Get run loop for the current thread
        NSRunLoop *commonRunLoop = [NSRunLoop currentRunLoop];
        [commonRunLoop run]; // Run without adding any events

        NSLog(@"β™» ️ β™» ️ % p % @", commonRunLoop, commonRunLoop); // Prints the address and contents of commonRunLoop
    }];
    [commonThread start];
}
NSLog(@"πŸ”ž END: % @", [NSThread currentThread]);

// Console print:
πŸ”ž START: <NSThread: 0x282efdac0>{number = 1, name = main}
πŸ”ž END: <NSThread: 0x282efdac0>{number = 1, name = main}
πŸƒβ€β™€οΈπŸƒβ€β™€οΈ <CommonThread: 0x282ea3600>{number = 5, name = (null)} / / the child threadβ™» ️ β™» ️0x281ffa940 <CFRunLoop 0x2807ff500 [0x20e729430]>{wakeup port = 0x9b03, stopped = false, ignoreWakeUps = true, 
current mode = (none),
common modes = <CFBasicHash 0x2835b32d0 [0x20e729430]>{type = mutable set, count = 1,
entries =>
    2 : <CFString 0x20e75fc78 [0x20e729430]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x2835b3360 [0x20e729430]>{type = mutable set, count = 1,
entries =>
    2 : <CFRunLoopMode 0x2800fca90 [0x20e729430]>{name = kCFRunLoopDefaultMode, port set = 0x9a03, queue = 0x2815f2880, source = 0x2815f3080 (not fired), timer port = 0x9803, 
    sources0 = (null), / / β¬… ️ empty
    sources1 = (null), / / β¬… ️ empty
    observers = (null), / / β¬… ️ empty
    timers = (null), / / β¬… ️ empty
    currently 629287011 (5987575088396) / soft deadline in: 7.68614087 e+11 sec (@ - 1) / hard deadline in: 7.68614087 e+11 sec (@ - 1)},}} πŸ€πŸ€πŸ€0x2814eb360 NSRunLoop -[NSRunLoop(Common) dealloc] // commonRunLoop Run loop object destroyed (run loop exit)πŸ€ πŸ€ πŸ€ < CommonThread:0x2836ddc40>{number = 6, name = (null)} CommonThread -[CommonThread dealloc] // commonThread thread object destroyed (thread exit)
Copy the code

After running the program, our commonThread exits and so does commonRunLoop. The run loop exits when there are no events in the current mode. As printed in the console above: sources0, sources1, observers, timers are all (null), so we need to create an event for the Run loop to handle so that the run loop does not exit. [commonRunLoop run] in our example code above; Add the following two lines above the line:

// Add source to NSDefaultRunLoopMode of run loop (add source Timer Observer)
[commonRunLoop addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

// Add timer
[[NSTimer alloc] init] [[NSTimer alloc] init
// NSTimer *time = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
/ / NSLog (@ "⏰ ⏰ % @", the timer).
// }];
// [[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];

NSLog(@"β™» ️ % p % @", commonRunLoop, commonRunLoop);

// Console part prints:. sources1 = <CFBasicHash0x60000251a4c0 [0x7fff8002e7f0]>{type = mutable set, count = 1,
entries =>
1 : <CFRunLoopSource 0x600001e700c0 [0x7fff8002e7f0]>{signalled = No, valid = Yes, order = 200, context = <CFMachPort 0x600001c7c370 [0x7fff8002e7f0]>{valid = Yes, por 
// ⬆️ CFMachPort 0x600001C7c370 is the NSPort we added.Copy the code

NSPort is added to mode sources1 and neither commonThread nor commonRunLoop is dealloc. [commonRunLoop run] [commonRunLoop run] [commonRunLoop run] NSLog(@”♻️♻️ %p %@”, commonRunLoop, commonRunLoop); UIApplicationMain itself doesn’t return because UIApplicationMain turned on its main run loop. So return 0 at the end; [commonRunLoop run]; [commonRunLoop run]; The run function itself does not return and opens the run loop for the current thread. This commonThread thread no longer exits and remains active. [commonRunLoop run]; The line can be thought of as a boundary, and the code below it will not be executed during commonRunLoop startup and will only be executed when commonRunLoop exits.

NSRunLoop: NSRunLoop: NSRunLoop: NSRunLoop: NSRunLoop: NSRunLoop: NSRunLoop: NSRunLoop: NSRunLoop: NSRunLoop

run

Run is an instance method of the NSRunLoop class. Its main functions are: Puts the receiver (NSRunLoop object) into a permanent loop, during which time it processes data from all attached input sources.

@interface NSRunLoop (NSRunLoopConveniences)

- (void)run;

// ⬇️ there are also two run functions that specify mode and limitDate
- (void)runUntilDate:(NSDate *)limitDate; - (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate; . @endCopy the code

This method exits immediately if no input sources or timers (NSTimer) are attached to the Run loop. Otherwise, it will run receiver (NSRunLoop object) in NSDefaultRunLoopMode mode by repeatedly calling runMode:beforeDate. In other words, this method starts an infinite loop that processes data from input Sources and Timers.

Manually deleting all known input sources and timers from the Run loop does not guarantee that the run loop will exit. MacOS can install and remove additional Input Sources as needed to handle requests for the Receiver’s Thread. Therefore, these sources can prevent the run loop from exiting.

This method should not be used if you want the Run loop to terminate. Instead, use one of the other run methods and check your other arbitrary conditions in the loop. A simple example is this:

BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
Copy the code

ShouldKeepRunning should be set to NO elsewhere in the program to terminate the run loop.

See the comment on the run function already explicitly tells us that run internally repeats runMode:beforeDate infinitely: If we want the run loop to run forever and the corresponding thread to never exit, we can use the run function to start the Run loop object. If you want to start or stop the Run loop arbitrarily according to the development scenario, you need to use two run functions with limitDate arguments and a while loop, as shown in the example code from Apple above. We’ll explain this in more detail later.

The CFRunLoopStop function (which is the stop function of the run loop, and will be explained in detail below) does not apply to the run loop started by calling the run function. Using CFRunLoopStop stops only runMode:beforeDate: in one loop, the run loop object calls runMode:beforeDate: again the next time the loop comes in.

NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while(1) {
    Bool resul = [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
Copy the code
[NSDate distantFuture]

[NSDate distantFuture] is an NSDate object representing a date (in centuries) in the distantFuture. This value can be passed when an NSDate object is needed to essentially ignore the date argument. You can use the object returned by distantFuture as the date parameter to wait indefinitely for the event to occur.

@property (class, readonly, copy) NSDate *distantFuture;
Copy the code

The current output of [NSDate distantFuture] is 4001-01-01 08:00:00. The actual time is 2020 12 11.

Later, we will verify the function point of manually exiting the Run loop. For the time being, we will verify whether we can dynamically add tasks to the thread that has started the Run loop.

Adds a task to the thread in which run Loop has been started

We need to modify the above test code. First we change the commonThread local variable above to a property of the ViewController.

@property (nonatomic, strong) CommonThread *commonThread;
Copy the code

Then assign the previous creation of the commonThread local variable to self.monthread, and add the following custom function rocket and ViewController’s touchesBegan:withEvent: method.

- (void)rocket:(NSObject *)param {
    sleep(1);
    NSLog(@"πŸš€ πŸš€ % @ param: % p", [NSThread currentThread], param);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"πŸ“Ί πŸ“Ί START...");
    
    / / wait at the end of parametric representation performSelector: onThread: withObject: whether the function such as @ the selector (rocket) completes before returning, or returned directly,
    // similar to dispatch_async and dispatch_sync, which indicates whether the @selector(rocket:) is performed asynchronously or synchronously in the self.commonThread.
    [self performSelector:@selector(rocket:) onThread:self.commonThread withObject:nil waitUntilDone:YES];
    
    NSLog(@"πŸ“Ί πŸ“Ί END...");
}
Copy the code

After the above code is edited, touch the blank area of the ViewController and see the Rocket function executing properly.

πŸ“Ί πŸ“Ί START... πŸš€ πŸš€ < CommonThread:0x281f8ce80>{number = 5, name = (null)} param: 0x0πŸ“Ί πŸ“Ί END...Copy the code

(here and found a point, click on the screen in a row, click a few times rocket function can perform several times, even in the performSelector: onThread: withObject: waitUntilDone: When we pass YES to the last argument, touchesBegan withEvent: When we click on the screen before the function is finished, the system still records how many times we clicked on the screen, and then the Rocket function executes the corresponding number of times. Using NSThread mainThread as the thread parameter will still execute the corresponding number of clicks, but there are still some differences between child threads and main threads. If you are interested, you can test this for yourself. (Actually, I really don’t know how to describe the difference)

Then we will run another test. If we annotate the run loop code in the self.monThread thread task, the rocket function will not execute when we touch the screen. If the performSelector: onThread: withObject: waitUntilDone: If we pass YES to the last argument of this function, it will crash, and we can see that the commonThread was a local variable and it will exit and be destroyed, even though one of the properties that we modified to make ViewController is strongly referenced, However, if the run loop of self.commonThread is not actively started, it is still inactive.

Stop the run loop of the started run loop thread

To learn how to stop the Run loop, first we add a stop button and click event to the ViewController. Add the following code:

// Stop button click event
- (IBAction)stopAction:(UIButton *)sender {
    NSLog(@"🎏 stop loop START (ACTION)...");
    [self performSelector:@selector(stopRunLoop:) onThread:self.commonThread withObject:nil waitUntilDone:NO];
    NSLog(@"🎏 stop loop END (ACTION)...");
}

// Stop run loop
- (void)stopRunLoop:(NSObject *)param {
    NSLog(@"🎏 stop loop START...");
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"🎏 stop loop END...");
}
Copy the code

After clicking the stop button, you can see that both functions execute normally. But we click on a blank area of the screen and see that the Rocket function still works.

 🎏 stop loop START(ACTION). 🎏 stop loopEND(ACTION). 🎏 stop loop START... 🎏 stop loop END... πŸ“Ί πŸ“Ί START... πŸš€ πŸš€ < CommonThread:0x2807c2a80> {number = 5, name = (null)} param: 0x0πŸ“Ί πŸ“Ί END...Copy the code

So we put [commonRunLoop run]; Modified to [commonRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]. And then after running the program, we directly click the stop button and see the following print on the console:

 🎏 stop loop START(ACTION). 🎏 stop loopEND(ACTION). 🎏 stop loop START... 🎏 stop loop END... ♻️♻️ 0x2819D6700 <CFRunLoop 0x2801D7000 [0x20E729430]>{wakeup port = 0x9b03, stopped = false, ignoreWakeUps = true, current mode = (none), ... πŸ€ πŸ€ πŸ€0x2819d6700 NSRunLoop -[NSRunLoop(Common) dealloc]
Copy the code

CFRunLoopStop (CFRunLoopStop) [commonRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; The function returns, and then the following NSLog(@”♻️♻️ %p %@”, commonRunLoop, commonRunLoop); When self.monthread is created, the block function added when self.monthread is created completes. When this logic is completed, commonRunLoop exits and is destroyed.

Now let’s do another case. Running the program again, we do not click the stop button, directly click the blank area of the screen, and see the following print on the console:

πŸ“Ί πŸ“Ί START... πŸ“Ί πŸ“Ί END... πŸš€ πŸš€ < CommonThread:0x280fddb00>{number = 5, name = (null)} param: 0x0β™» ️ β™» ️0x283e86b80 <CFRunLoop 0x282687900 [0x20e729430]>{wakeup port = 0x9b03, stopped = false, ignoreWakeUps = true, current mode = (none), ... πŸ€ πŸ€ πŸ€0x283e86b80 NSRunLoop -[NSRunLoop(Common) dealloc]
Copy the code

We did not execute the CFRunLoopStop function this time, and only executed an event on the self.monthread thread. After executing the rocket function once, [commonRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; The function returns, and then the following NSLog(@”♻️♻️ %p %@”, commonRunLoop, commonRunLoop); When self.monthread is created, the block function added when self.monthread is created completes. When this logic is completed, commonRunLoop exits and is destroyed. (This also verifies that the thread does not hold a run loop, and the run loop can exit and destroy itself while the thread object is still held by the viewController.)

So let’s change the code to look like the Apple sample code based on comments in the run function. Add a Boolean attribute shouldKeepRunning and start with YES, Then put [commonRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]. Is modified to the while (self shouldKeepRunning && [commonRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]). , and then run each test, can find print results and [commonRunLoop run]; Exactly the same in use.

Let’s optimize our code by adding the __weak modifier self to prevent cyclic references:

NSLog(@"πŸ”ž START: % @", [NSThread currentThread]);
{
    __weak typeof(self) _self = self;
    self.commonThread = [[CommonThread alloc] initWithBlock:^{
        NSLog(@"πŸƒ ‍ ♀ ️ πŸƒ ‍ ♀ ️ % @", [NSThread currentThread]);
        
        NSRunLoop *commonRunLoop = [NSRunLoop currentRunLoop];
        
        // Add Source Timer Observer to run loop
        [commonRunLoop addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        
        // NSTimer *time = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        / / NSLog (@ "⏰ ⏰ % @", the timer).
        // }];
        // [[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
        
        NSLog(@"β™» ️ % p % @", commonRunLoop, commonRunLoop);
        __strong typeof(_self) self = _self;
        while (self && self.shouldKeepRunning && [commonRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
        NSLog(@"β™» ️ β™» ️ % p % @", commonRunLoop, commonRunLoop);
    }];
    
    [self.commonThread start];
}

NSLog(@"πŸ”ž END: % @", [NSThread currentThread]);
Copy the code

ShouldKeepRunning = NO when the stop button is clicked to ensure that commonRunLoop stops after CFRunLoopStop and does not enter the while loop again.

- (void)stopRunLoop:(NSObject *)param {
    NSLog(@"🎏 stop loop START...");
    self.shouldKeepRunning = NO;
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"🎏 stop loop END...");
}
Copy the code

Run the program. Click the blank area of the screen and the Rocket function will execute normally. Click the Stop button and see the run loop of the self.monThread exits and the Run loop object is destroyed. The current ViewControler returns to the previous controller (the jump logic can be added by itself), and the current controller and self.commonThread are destroyed normally. Here is the console print:

 πŸ”ž START: <NSThread: 0x283cfda80>{number = 1, name = main}
 πŸ”ž END: <NSThread: 0x283cfda80>{number = 1, name = main}
 πŸƒβ€β™€οΈπŸƒβ€β™€οΈ <CommonThread: 0x283c84180>{number = 6, name = (null)} // ⬅️ self.commonThread The sub-thread is startedβ™» ️0x280dfdce0 <CFRunLoop 0x2815f9500 [0x20e729430]>{wakeup port = 0x1507, stopped = false, ignoreWakeUps = true, 
current mode = (none), // ⬅️ normally obtains the run loop of self.monthread and adds an event to it to prevent the run loop from exiting when no event occurs. πŸ“Ί πŸ“Ί START... πŸ“Ί πŸ“Ί END... πŸš€ πŸš€ < CommonThread:0x283c84180>{number = 6, name = (null)} param: 0x0 // ⬅️ Touch screen Adding tasks to the active self.monthread thread works normally
 🎏 stop loop START(ACTION)... // ⬅️ Click the stop button to stop the run loop of self.monthread
 🎏 stop loop END(ACTION)... 🎏 stop loop START... 🎏 stop loop END... β™» ️ β™» ️0x280dfdce0 <CFRunLoop 0x2815f9500 [0x20e729430]>{wakeup port = 0x1507, stopped = false, ignoreWakeUps = true, 
current mode = (none), // The run loop of the self.monthread thread has stopped. The block function added when the self.monthread is created continues
...

 πŸ€πŸ€πŸ€ 0x280dfdce0 NSRunLoop -[NSRunLoop(Common) dealloc] // The run loop of the selfthread exits and the run loop object is destroyed normallyπŸ€ πŸ€ πŸ€ < ViewController:0x10151b630> ViewController -[ViewController dealloc] // The current controller is destroyed after popπŸ€ πŸ€ πŸ€ < CommonThread:0x283c84180>{number = 6, name = main} CommonThread -[CommonThread dealloc] The self.monthread thread object is also destroyed normally
Copy the code

The above is under completely manual control: start the thread run loop, dynamically add tasks to the thread that has run loop enabled, and manually stop the thread that has run loop enabled. From this, we have a general understanding of the thread activity that run loop keeps. According to the NSThread and NSRunLoop classes provided by Apple, learning threads and Run loops in an object-oriented way really helps us understand some conceptual things better.

Now let’s optimize all the above code as a whole according to some important points.

PerformSelector: onThread: withObject: waitUntilDone: If a thread does not meet the above conditions, it will not be able to execute the event passed by the selector parameter. It would be completely impossible to wait to pass NO without waiting for the selector to complete. So, we are all the performSelector: onThread: withObject: waitUntilDone: function can add a line before the if (! self.commonThread) return; If the viewController does not release the self.monthread reference after the self.monthread is created, Self.com monThread will not be nil (nor will it be nil if it is released), However, we manually set self.monthread to nil after executing the CFRunLoopStop stop function on the run loop of self.monthread. After all, the inactive thread is no different from the one that is already nil.

Since we have already started the run loop for self.monThread when we create it, we can guarantee that the self.monthread will remain active when we add events to it.

There is also a very subtle point. When we use a block, we use the __weak modifier outside the block to get a weak reference to self, and then use the __strong modifier inside the block to get a strong reference to the weak reference to self. First of all, it’s inside the block, and when the block finishes, it automatically frees the strongly-referenced self, just to make sure that self doesn’t get freed during the block, which by default extends the life of self until the block ends, This doesn’t have any problems in our daily development, but, but, but, it doesn’t work in a run loop, when we push directly into the ViewController and pop directly back to the previous page, We need to use the dealloc function of the ViewController to stop the self.monthread’s run loop. If we also use the __strong modifier to get a strong reference to self, Then runMode:beforeDate of the run loop inside the block when self.monthread is created: The startup function does not return, which potentially extends the life of self, directly causing the ViewController to fail to release, and the dealloc function to fail to be called.

Here are some explanations for the use of __weak and __strong pairings, but refer to the previous article if blocks are unclear.

// The following block to be executed in the parallel queue has no retain self
__weak typeof(self) _self = self;
dispatch_async(globalQueue_DEFAULT, ^{
    // Ensure that self is not released during the following execution, and that self performs a release after execution.
    
    // In ARC, it looks like __wek and __strong cancel each other out,
    // Where self, __strong, performs a release when the right curly braces below it.
    __strong self will have a value only if _self has a value when this block is executed.
    // Otherwise, the following if judgment will return directly.
    
    __strong typeof(_self) self = _self;
    if(! self)return;
    
    // do something
    // ...
    
    dispatch_async(dispatch_get_main_queue(), ^ {// If you can come in, self exists
        self.view.backgroundColor = [UIColor redColor];
    });
});
Copy the code

Here is all the code that corresponds to the explanation above.

#import "ViewController.h" #import "CommonThread.h" @interface ViewController () @property (nonatomic, strong) CommonThread *commonThread; @property (nonatomic, assign) BOOL shouldKeepRunning; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.shouldKeepRunning = YES; NSLog(@"πŸ”ž START: %@", NSThread currentThread); // use a weak reference to self inside the block below ⬇️, otherwise vc cannot destroy __weak Typeof (self) _self = self; Self.com monThread = [[CommonThread alloc] initWithBlock:^{NSLog(@"πŸƒβ™€οΈπŸƒβ™€οΈ %@", self.commonThread = [[CommonThread alloc] initWithBlock:^{NSLog(@"πŸƒβ™€οΈπŸƒβ™€οΈ %@", [NSThread currentThread]); NSRunLoop *commonRunLoop = [NSRunLoop currentRunLoop] The Source/Timer/Observer [commonRunLoop addPort: [[NSPort alloc] init] forMode: NSDefaultRunLoopMode]; NSLog (@ "β™» ️ % p % @". CommonRunLoop, commonRunLoop); // ⬇️ no longer uses __strong to refer to external _self, // __strong typeof(_self) self = _self; while (_self &&_self. shouldKeepRunning) {[commonRunLoop RunMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];} NSLog (@ "β™» ️ β™» ️ % p % @", commonRunLoop, commonRunLoop); }]; [self.commonThread start]; NSLog(@"πŸ”ž END: %@", [NSThread currentThread]); } - (void)rocket:(NSObject *)param { // sleep(3); NSLog(@"πŸš€πŸš€ % @param: %p", [NSThread currentThread], param); } - (void)touch began :(NSSet< touch *> *)touches withEvent:(UIEvent *)event {NSLog(@"πŸ“ΊπŸ“Ί START..." ); if (! self.commonThread) return; Self.monthread is nil. Self.monthread is nil. The following wait parameter is YES [self performSelector: @ the selector (rocket) onThread:self.com monThread withObject: nil waitUntilDone: YES]; NSLog (@ "πŸ“Ί πŸ“Ί END..." ); } - (IBAction)stopAction:(UIButton *)sender {NSLog(@"🎏 stop loop START(ACTION)..." ); if (! self.commonThread) return; Self.monthread is nil. Self.monthread is nil. The following wait parameter is YES [self performSelector: @ the selector (stopRunLoop:) onThread:self.com monThread withObject: nil waitUntilDone:YES]; NSLog (@ 🎏 stop loop END "(ACTION)..." ); } - (void)stopRunLoop:(NSObject *)param {NSLog(@"🎏 stop loop START..." ); self.shouldKeepRunning = NO; CFRunLoopStop(CFRunLoopGetCurrent()); // Stop the current thread's run loop self.monthread = nil; NSLog(@"🎏 stop loop END...") ); } - (void)dealloc { [self stopAction:nil]; Self.monthread run loop NSLog(@"πŸ€πŸ€πŸ€ % @viewController %s", self, __func__); } @endCopy the code

See here we should have a general understanding of the relationship between run loop and thread. Of course, the function of Run loop is not only thread survival, but also many other aspects of the application. In the next chapter, we will enter into a comprehensive study of run loop. Nsrunloop. h = CFRunLoopRef = CFRunLoopRef = CFRunLoopRef

. 🎬

We should have a general understanding of the relationship between run loop and thread. Of course, the function of Run loop is not only for thread survival, but also many other aspects of application. So let’s take NSRunLoop class provided by Apple as a starting point to learn run loop.

NSRunLoop

The programmatic interface to objects that manage input sources. A programming interface for objects that manage input sources. (Object-oriented programming interface to manage input sources)

@interface NSRunLoop : NSObject {
@private
    id          _rl;
    id          _dperf;
    id          _perft;
    id          _info;
    id        _ports;
    void    *_reserved[6];
}
Copy the code

The NSRunLoop object handles input from sources such as mouse and keyboard events from the Window System, NSPort objects, and NSConnection objects. The NSRunLoop object also handles NSTimer events.

Your application neither creates nor explicitly manages NSRunLoop objects. Each NSThread object (including the main thread of the application) has an NSRunLoop object that is automatically created for it as needed. If you need to access the current thread’s Run loop, use the currentRunLoop class method to do so. Note that from the perspective of NSRunLoop, NSTimer objects are not “input” — they are a special type, which means that when fired they do not cause a run loop to return. (As in the previous example code, runMode:beforeDate: is not wrapped by the while loop Function, after we click the screen once and make it receive an event, the Run loop will exit. If we just add a timer, the Run loop will continue to receive the timer callback without exiting.

The NSRunLoop class is not generally considered thread-safe, and its methods can only be called in the context of the current thread. Never attempt to call a method of an NSRunLoop object running in another thread, as doing so may result in unexpected results.

currentRunLoop

CurrentRunLoop Returns the run loop of the current thread. The return value is the NSRunLoop object of the current thread. If the thread does not already have a run loop, a run loop is created for it and returned.

@property (class, readonly, strong) NSRunLoop *currentRunLoop;
Copy the code

currentMode

CurrentMode is the current run Loop mode of the receiver (NSRunLoop instance object).

@property (nullable, readonly, copy) NSRunLoopMode currentMode;
Copy the code

The receiver’s current run Loop mode, which returns the current run mode only when the receiver runs, or nil otherwise. Current mode by running the run loop method Settings, such as acceptInputForMode: beforeDate: and runMode: beforeDate:.

limitDateForMode:

LimitDateForMode: Performs a loop through the Run loop in the specified mode and returns the date when the scheduled next timer is triggered.

- (nullable NSDate *)limitDateForMode:(NSRunLoopMode)mode;
Copy the code

Mode: Search by running loop mode. You can specify custom Modes or use one of the Modes listed in Run Loop Modes.

The return value is the date on which the next timer schedule is triggered; Nil if there is no input source for this mode.

The run loop immediately times out when it enters, so if there is no input sources to process, the run loop does not block and waits for input.

mainRunLoop

MainRunLoop Returns the run loop of the main thread. The return value is the Run loop object representing the main thread.

@property (class, readonly, strong) NSRunLoop *mainRunLoop API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
Copy the code

getCFRunLoop

GetCFRunLoop returns the underlying CFRunLoop object of the receiver (NSRunLoop instance object).

- (CFRunLoopRef)getCFRunLoop CF_RETURNS_NOT_RETAINED;
Copy the code

You can use the returned run loop to configure the current run loop with a Core Foundation function call. For example, you can use this function to set up the Run Loop Observer.

NSRunLoop encapsulates the __CFRunLoop structure of the Core Foundation framework. So all run loops mentioned above can be understood at the code level as NSRunLoop objects or instances of the __CFRunLoop structure. The encapsulation of NSRunLoop allows us to learn and use run loop in a more object-oriented way, and inherits from NSObject to automatically handle memory requisition and release using ARC, And each NSRunLoop object corresponds to an instance of the __CFRunLoop structure, The getCFRunLoop function allows us to take an NSRunLoop object and get its corresponding CFRunLoopRef (__CFRunLoop structure pointer) and then from CFRunLoopRef we can perform more operations on the run loop, There are a number of __CFRunLoop functions that are not implemented in NSRunLoop, such as the run loop observer: Void CFRunLoopAddObserver(CFRunLoopRef RL, CFRunLoopObserverRef Observer, CFRunLoopMode mode)

NSRunLoopMode

NSRunLoopMode is essentially an NSString, indicating the running mode of NSRunLoop.

typedef NSString * NSRunLoopMode NS_EXTENSIBLE_STRING_ENUM;
Copy the code

NSRunLoop defines the following run loop modes.

  • NSDefaultRunLoopMode
const NSRunLoopMode NSDefaultRunLoopMode;
Copy the code

The mode that handles input sources other than NSConnection objects is also the most common run loop mode.

  • NSRunLoopCommonModes
const NSRunLoopMode NSRunLoopCommonModes;
Copy the code

Objects added to the Run loop using this value as mode are monitored by all run loop modes declared as members of the “common” mode set; For details, see the description of CFRunLoopAddCommonMode. (You can think of NSRunLoopCommonModes as a collection of run loop modes, including all modes marked common.)

  • UITrackingRunLoopMode
const NSRunLoopMode UITrackingRunLoopMode;
Copy the code

The mode set when tracking in Controls is performed, such as when the screen is swiped. You can use this mode to add timers that are triggered during tracing.

NSConnection and NSApplication define additional run loop patterns, including the following:

  • NSConnectionReplyMode
NSString *const NSConnectionReplyMode;
Copy the code

Indicates the mode of the NSConnection object waiting to connect. You almost never need to use this pattern.

  • NSModalPanelRunLoopMode
NSRunLoopMode NSModalPanelRunLoopMode;
Copy the code

Run Loop should be set to this mode while waiting for input from a Modal Panel such as NSSavePanel or NSOpenPanel.

  • NSEventTrackingRunLoopMode
NSRunLoopMode NSEventTrackingRunLoopMode;
Copy the code

When tracking Events modally (such as mouse drag loop, scrollView scroll, etc.), run Loop should be set to this mode.

addTimer:forMode:

AddTimer :forMode: Registers the given timer with the given input mode.

- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;
Copy the code

You can add timers to multiple input modes. When running in the specified mode, the receiver (NSRunLoop instance object) causes the timer to fire at or after a predetermined firing date. When triggered, the timer calls its associated handler routine selector, which is a selector on the specified object (the NSTimer object).

The recipient retain Timer, that is, the NSRunLoop instance object will hold the NSTimer object. To remove the timer from all run Loop modes where the timer is installed, send an invalidate message to the timer.

addPort:forMode:

AddPort :forMode: Adds port as input source to the specified mode of the run loop.

- (void)addPort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;
Copy the code

The port of the schedules receiver (NSRunLoop instance object) for this method. You can add a port to multiple input modes. When the receiver (NSRunLoop instance object) is running in the specified mode, it dispatches messages to that port to the specified handler routine for that port.

removePort:forMode:

RemovePort :forMode: Removes the port from the specified Run Looop mode of the Run loop.

- (void)removePort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;
Copy the code

If you add ports to multiple Run loop modes, you must remove them from each mode separately.

runMode:beforeDate:

RunMode :beforeDate: Run run loop once, blocking before the specified date to specify mode input.

- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
Copy the code

Returns YES if the run loop runs and processes an input source or reaches the specified timeout value; Otherwise, if the run loop cannot be started, the value is NO. RunMode :beforeDate: returns YES when an input source is processed. BeforeDate: returns YES when an input source is processed. Starting the run loop will block the block self.monthread to continue, and after we touch the screen to execute the rocket once, runMode:beforeDate: The function returns the block and continues, and the run loop ends and the run loop exits.)

If NO input sources or timers are attached to the Run loop, this method exits immediately and returns NO; Otherwise, it will return after processing the first input source or reaching the limitDate. Manually deleting all known input sources and timers from the Run loop does not guarantee that the run loop will exit immediately. MacOS can install and remove additional input Sources as needed to handle requests to the recipient thread (NSRunLoop instance object). Therefore, these sources can prevent the run loop from exiting.

Tip: Timer is not treated as input source and may fire multiple times while waiting for this method to return. That is, we use addTimer:forMode: to add a timer to the NSRunLoop object, and the NSRunLoop object has only one timer, then use runMode:beforeDate only without the while loop: If the run loop is started, the selector of the timer will always be executed. Execution of the timer does not cause runMode:beforeDate to be returned just once as input source does.

runUntilDate:

RunUntilDate: Run loop runs until the specified date, during which time it processes data from all additional input sources.

- (void)runUntilDate:(NSDate *)limitDate;
Copy the code

This method exits immediately if no input sources or timers are attached to the Run loop; Otherwise, it runs the receiver (the NSRunLoop instance object) in NSDefaultRunLoopMode by repeatedly calling runMode:beforeDate: until the specified expiration date. Manually deleting all known input sources and timers from the Run loop does not guarantee that the run loop will exit immediately. MacOS can install and remove additional input Sources as needed to handle requests to the recipient thread (NSRunLoop instance object). Therefore, these sources can prevent the run loop from exiting.

acceptInputForMode:beforeDate:

AcceptInputForMode: beforeDate: run run loop or until a specified date at a time, only accept specifies input mode.

- (void)acceptInputForMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
Copy the code

This method exits immediately if no input sources or timers are attached to the Run loop; Otherwise, it runs a Run loop, which returns once an input source has processed a message or a specified amount of time has elapsed.

Tip: Timer is not treated as input source and may fire multiple times while waiting for this method to return.

Manually deleting all known input sources and timers from the Run loop does not guarantee that the run loop will exit immediately. MacOS can install and remove additional input Sources as needed to handle requests to the recipient thread (NSRunLoop instance object). Therefore, these sources can prevent the run loop from exiting.

performInModes:block:

PerformInModes: blocks: schedules the execution of blocks on the target Run loop in the given modes.

- (void)performInModes:(NSArray<NSRunLoopMode> *)modes block:(void(^) (void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
Copy the code

Modes: Array of run loop modes on which blocks can be executed.

performBlock:

PerformBlock: Schedules performs a block on the target Run loop.

- (void)performBlock:(void(^) (void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
Copy the code

NSRunLoop+NSOrderedPerform

performSelector:target:argument:order:modes:

Schedule messages to be sent on the recipient (NSRunLoop instance object).

- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes;
Copy the code

ASelector: aSelector that identifies the method to call. This method should have no obvious return value and should take a single parameter of type ID.

Target: The object that defines the selector in aSelector.

AnArgument: The argument passed to the method when called. If the method does not accept arguments, nil is passed.

Order: priority of the message. If multiple messages are planned, the message with the lower order value is sent before the message with the higher order value.

Modes: Array of input modes for which messages can be sent. You can specify custom Modes or use one of the Modes listed in Run Loop Modes.

This method sets up a timer to execute the aSelector message on the receiver (NSRunLoop instance object) at the beginning of the next run Loop iteration. Timer is configured to run in the modes specified by the modes parameter. When the timer fires, the thread attempts to fetch the message from the Run loop and execute the selector. If run loop is running and in one of the specified modes, it succeeds; Otherwise, the timer will wait until the Run loop is in one of these modes.

This method returns before sending the aSelector message. The receiver holds the target and anArgument objects until the selector’s timer fires, and then releases them as part of the cleanup.

Use this method if you want to send multiple messages after processing the current event, and you want to ensure that the messages are sent in a specific order.

cancelPerformSelector:target:argument:

CancelPerformSelector: target: argument: cancel the previous arrangement of messages sent.

- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg;
Copy the code

You can use this method to cancel before use performSelector: target: argument: order: modes: scheduled way. These parameters identify the message you want to cancel and must match the message originally specified in the schedule selector. This method removes perform requests from all modes of the Run loop.

cancelPerformSelectorsWithTarget:

Any cancellation cancelPerformSelectorsWithTarget: all the unfinished order performs a given target.

- (void)cancelPerformSelectorsWithTarget:(id)target;
Copy the code

This method cancels the previous Scheduled message associated with target, ignoring the selectors and arguments for the Scheduled operation. This and cancelPerformSelector: target: argument: on the contrary, the latter requires you to match the selector and argument, and target. This method removes Perform requests for the object from all modes of the Run loop.

NSObject+NSDelayedPerforming

performSelector:withObject:afterDelay:inModes:

PerformSelector: withObject: afterDelay: inModes: after the delay using the specified mode on the current thread calls the receiver (NSObject object and its subclasses) method.

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
Copy the code

ASelector: aSelector that identifies the method to call. The method should have no obvious return value (void), and should take a single argument of type ID, or no arguments.

AnArgument: The argument passed to the method when called. If the method does not accept arguments, nil is passed.

Delay: Minimum time before sending a message. Specifying a delay of 0 does not necessarily cause the selector to execute immediately. The selector is still queued in the thread’s Run loop and executed as soon as possible.

Modes: An array of strings that identify the modes associated with the timer that executes the selector. This array must contain at least one string. If you specify nil or an empty array for this argument, this method returns without executing the specified selector.

This method sets up a timer to execute aSelector messages on the run loop of the current thread. The timer configuration runs in the modes specified by the modes parameter. When the timer fires, the thread attempts to fetch the message from the Run loop and execute the selector. If run loop is running and in one of the specified modes, it succeeds; Otherwise, the timer will wait until the Run loop is in one of these modes.

If you want to run the loop in the default mode of make out a message, please use the performSelector: withObject: afterDelay: inModes: method. If you are unsure whether the current thread is given priority to the thread, you can use performSelectorOnMainThread: withObject: waitUntilDone: Or performSelectorOnMainThread: withObject: waitUntilDone: modes: method to ensure that the selector on the main thread. To cancel the line message, please use the cancelPreviousPerformRequestsWithTarget: or cancelPreviousPerformRequestsWithTarget: selector: object: method.

This method registers with the Runloop of its current context and depends on runloops that run periodically for proper execution. A common context is when scheduling queue calls, you might call this method and end up registering with a Runloop that doesn’t automatically run periodically. If such functionality is required when running on a dispatch queue, use dispatch_after and related methods to obtain the desired behavior. (Similarly, NSTimer is not always available, you can use dispatch_source instead)

performSelector:withObject:afterDelay:

PerformSelector: withObject: afterDelay: same as above, using the default mode.

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
Copy the code

cancelPreviousPerformRequestsWithTarget:selector:object:

CancelPreviousPerformRequestsWithTarget: selector: object: cancel have previously in performSelector: withObject: afterDelay: registered in the execution of the request.

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
Copy the code

AnArgument: previous use performSelector: withObject: afterDelay: instance methods registration request parameters. Parameter equality is determined using isEqual:, so the value need not be the same as the object originally passed. Pass nil to match the request for nil that was originally passed as an argument.

All execution requests with the same target as aTarget, the same arguments as aArgument, and the same selector as aSelector are cancelled. This method deletes execution requests only in the current run loop, not in all run loops.

cancelPreviousPerformRequestsWithTarget:

CancelPreviousPerformRequestsWithTarget: cancel have previously use performSelector: withObject: afterDelay: instance methods registered the execution of the request.

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
Copy the code

ATarget: have previously by performSelector: withObject: afterDelay: instance methods registration request.

All execution requests with the same target aTarget will be cancelled. This method deletes execution requests only in the current run loop, not in all run loops.

So that’s the documentation for everything in the nsrunloop. h file,

Refer to the link

Reference link :πŸ”—

  • Runloop source
  • Run Loops official documentation
  • Complete guide to iOS RunLoop
  • IOS source code parsing: Runloop underlying data structure
  • IOS source code: Runloop operating principle
  • Understand RunLoop in depth
  • IOS Basics – Dive into RunLoop
  • A core runloop source code analysis
  • NSRunLoop
  • Get to the bottom of iOS – Understand RunLoop in depth