“Master, recently I was reading about thread manipulation and found a problem. My child thread will destroy after it finishes the task, and then I need to start another child thread, but why can I open an APP at 18 o ‘clock on my iPhone and keep running? “

“Oh, shaoxia means this question ah, easy. Why does it keep working? Because the battery didn’t run out. “

“…… “

“Ha, ha, ha, ha, ha, ha, ha, ha, ha, so easy to learn, my husband will grant you RunLoop to use big method and RunLoop internal work mind method, integrate these two secrets, the operating mechanism of iPhone18 will become more and more clear in your eyes.”

RunLoop uses the large method

It is mainly used in the following aspects in daily development:

  1. NSTimerRelated to use
  2. The child thread is alive
  3. Perform SelectorThe use of
  4. Deeper operation
  1. NSTimer also works when the main thread is used, and when the list is swiped.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code

When NSTimer is created, it runs in NSRunLoopDefaultMode of the main thread by default. When UIScrollView is rolled, it switches to UITrackingRunLoopMode and the timer stops calling. To make the timer call correctly, you can manually add RunLoop to NSRunLoopCommonModes so that the timer will be called correctly.

  1. Use NSTimer in child threads.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil  repeats:YES]; NSRunLoop *runloop = [NSRunLoop currentRunLoop]; [runloop addTimer:timerforMode:NSDefaultRunLoopMode];
     [runloop run];
 });
Copy the code

The RunLoop in the child thread is created on the first fetch, and after adding the timer to the corresponding Mode, the timer in the child thread works. Note: If you want to cancel a timer in a child thread, cancel it in the corresponding child thread as well.

  1. The child thread is alive. A demonstration application is available in AfNetworking2.x.
+ (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

The RunLoop for the main thread is enabled by default, and the RunLoop for the child thread is disabled by default, so the child thread is destroyed after completing a task. If you want to keep child threads alive, you need to create runloops. NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; If this is the case, you will find that the child thread still exits after completing the task. Why does this happen? Because a RunLoop wants to run in a mode, it needs a Source, Timer, and Observer in the mode. When none of these are present, the RunLoop exits. So I add [runLoop addPort: [NSMachPort port] forMode: NSDefaultRunLoopMode]; If a Source is added, the thread’s RunLoop will continue running and the background thread will survive successfully.

  1. Perform SelectorThe use of
- (void)viewDidLoad {
   [super viewDidLoad];    
   [self performSelector:@selector(onMainThread) withObject:nil afterDelay:3];
 
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [[NSThread currentThread] setName:@"gaga"];
       [self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
   });

}

- (void)onMainThread {
   NSLog(@"% @", [NSThread currentThread]);
   NSLog(@"% @", NSStringFromSelector(_cmd));
}

- (void)onOtherThread {
   NSLog(@"% @", [NSThread currentThread]);
   NSLog(@"% @", NSStringFromSelector(_cmd));
}
Copy the code

Respectively in the main thread, and the child thread calls performSelector: withObject: afterDelay:, found that the main thread of the normal operation, but the child thread cannot perform the call.

PerformSelector: withObject: afterDelay delay operation is equivalent to create a Timer is added to the current thread RunLoop, for the thread of the RunLoop, so you can perform normally. The RunLoop for the child thread is created on the first fetch, because we did not manually fetch the RunLoop for the child thread, so it will not execute properly.

Modify the child thread as follows to execute normally

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[NSThread currentThread] setName:@"gaga"];
        [self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop run];
    });
Copy the code

The Timer is visible in the RunLoop of the printed child thread, confirming the idea that a Timer would be added to a delayed operation.

- (void)onOtherThread {
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    NSLog(@"% @", runLoop); } Timers = <CFArray 0x600001FA6D60 [0x10DA31AE8]>{type = mutable-small, count = 0, values = ()},
	currently 589343081 (174839084728530) / soft deadline in+ +10 SEC (@-1)/Hard Deadlinein+1 +1 SEC (@-1)Copy the code

“Little xia, think about if it’s not delay, directly in the main thread and the child thread use performSelector: withObject:, normally call?”

“Well…”

“The answer is normally call because direct use performSelector: withObject:, the equivalent of a method call, does not involve Runloop.”

  1. Other operating

Of course, there are other applications, such as executing the performSelector method in a child thread and finding that the method cannot be called without creating a RunLoop. I’ll leave it to shaoxia to figure it out.

Another application for iOS performance tuning (Intermediate +) is asynchronous drawing, adding an Observer, and executing the desired code at the right time during a RunLoop run.

“Watch out for this. These methods cover most of RunLoop’s application layer uses.”

“Master, this martial arts training up, really let people smooth meridians, physical and mental comfort.”

“But these are just moves. If Shaoxia wants to better understand the operating mechanism of the iPhone, it needs to cultivate the corresponding internal skills to give full play to the maximum power of its martial arts.”

RunLoop internal work method

Threads correspond to runloops one by one, and their relationships are 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. RunLoop cannot be created directly, only obtained. Two auto-fetching functions are provided: CFRunLoopGetMain() and CFRunLoopGetCurrent(), and its RunLoop can only be fetched inside a thread (except for the main thread). These relationships can be viewed by observing the cfrunloop. c file in the CF source code, _CFRunLoopGet0 method.

CFRunLoopRef CFRunLoopModeRef CFRunLoopSourceRef CFRunLoopTimerRef CFRunLoopObserverRef A RunLoop contains multiple modes, each of which contains multiple sources/timers/observers. Each time the main function of RunLoop is called, only one Mode can be specified. This Mode is called CurrentMode. If you need to switch modes, you must exit the Loop and re-specify another Mode to enter. The main purpose of this is to separate the Source/Timer/Observer groups from each other. If there is no item in a mode, the RunLoop exits without entering the loop.

Attached is a RunLoop diagram