1. First experience of RunLoop~Thinking

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%ld",(long)count++);
    }];

[self.view addSubview:self.tv_list];

I wrote one UITableViewand then I added one NSTimer, what happened next? When swiping UITableView, the NSTimertimer stops. I believe that most developers have encountered this problem. Of course, Baidu will have many answers.

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

Adding this code solves the problem perfectly, but why is this happening? The guess must be related to NSRunLoop. So what is NSRunLoop?

1. What is NSRunLoop?

Screenshot 2022-07-04 14.21.09.png

NSRunLoopNSPortObjects handle input from sources, such as mouse and keyboard events from the window system and objects. NSRunLoopObjects also handle NSTimerevents.

Your application neither creates nor explicitly manages NSRunLoopobjects. NSThreadThe system creates one object per object as needed NSRunLoop, including the main thread of the application. If you need access to the current thread's run loop, use a class method currentRunLoop.

Note that objects are not "inputs" from NSRunLoopthe point of view NSTimer- they are a special type that don't cause the run loop to return when they trigger the run loop.

Can't seem to understand what it means. If it is easy to understand, this thing is an infinite loop. When there is an event response, RunLoop can respond to and process some events. When there is no event response, RunLoop will go to sleep, and it corresponds to the thread one-to-one. A thread can only have one RunLoop if there are any.

2. What is NSRunLoopMode?

Screenshot 2022-07-04 14.50.55.png

Modes that a run loop operates in. Please translate it yourself.

Screenshot 2022-07-04 15.00.32.png

At present, there are five kinds of NSRunLoopMode, but only NSDefaultRunLoopMode UITrackingRunLoopModel NSRunLoopCommonModesthese three are used on iOS, and the remaining two are used on MacOS.

NSDefaultRunLoopMode

Screenshot 2022-07-04 15.05.02.png

Set the mode to handle input sources other than connection objects.

UITrackingRunLoopModel

Screenshot 2022-07-04 15.05.56.png

The mode to set when tracking in the control.

NSRunLoopCommonModes

Screenshot 2022-07-04 15.06.41.png

包含一个或多个其他运行循环模式的伪模式。

其实这个伪模式中包含了NSDefaultRunLoopMode|UITrackingRunLoopModel这两种模式

3、测试

__block NSInteger count = 0;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"%ld",(long)count++);
    //添加这行代码
    NSLog(@"Timer的currentModel=%@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
**RunLoopTest[58445:4388936] Timer的currentModel=kCFRunLoopDefaultMode**

NSTimer在运行过程中打印出来的Mode为kCFRunLoopDefaultMode这个mode其实这个mode就等于NSDefaultRunLoopMode 因为NSRunLoop是基于CKRunLoop开发的。所以NSTimer在计时时RunLoop的Model为NSDefaultRunLoopMode

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    NSLog(@"TableView滑动---%@", [[NSRunLoop currentRunLoop] currentMode]);
}

接下来在scrollViewDidScroll这个代理方法里面添加上滑动监听

**RunLoopTest[58641:4397470] TableView滑动---UITrackingRunLoopMode**

所以基本上可以分析出来为什么在UITableView滑动的时候NSTimer计时不起作用,因为在UITableView滑动的时候当前主线程的RunLoop的Mode切换到了UITrackingRunLoopMode这个模式但是NSTimer只在NSDefaultRunLoopMode做响应,当UITableView停止的时候RunLoop的Mode又会切回到NSDefaultRunLoopMode,所以如果把NSTimer在当前RunLoop中的模式添加为NSRunLoopCommonModes那其实可以理解为NSTimer可以在NSDefaultRunLoopMode | UITrackingRunLoopModel这两种模式中都起作用。

当添加了[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];这行代码后:

**RunLoopTest[80618:4452646] 0**
**RunLoopTest[80618:4452646] Timer的currentModel=kCFRunLoopDefaultMode**
**RunLoopTest[80618:4452646] 1**
**RunLoopTest[80618:4452646] Timer的currentModel=kCFRunLoopDefaultMode**
**RunLoopTest[80618:4452646] TableView滑动---UITrackingRunLoopMode**
**RunLoopTest[80618:4452646] 2**
**RunLoopTest[80618:4452646] Timer的currentModel=UITrackingRunLoopMode**

可以发现当UITableView滑动的时候确实Timer的Mode变为了UITrackingRunLoopMode

接下来我如果把Timer的Mode直接添加成UITrackingRunLoopMode是否可行呢?

//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
**RunLoopTest[80891:4465038] 0**
**RunLoopTest[80891:4465038] Timer的currentModel=kCFRunLoopDefaultMode**
**RunLoopTest[80891:4465038] TableView滑动---UITrackingRunLoopMode**
**RunLoopTest[80891:4465038] TableView滑动---UITrackingRunLoopMode**
**RunLoopTest[80891:4465038] 1**
**RunLoopTest[80891:4465038] Timer的currentModel=UITrackingRunLoopMode**

证明也是可以一变滑动一遍计时,效果是一样的。

更多的思考

上面提到了一个线程只会对应一个RunLoop,那如果在开辟一个线程去做计时但是不添加Timer的Model是否可以实现一样的效果呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    //开启线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimer) object:nil];
    [thread start];
    [self.view addSubview:self.tv_list];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

}
-(void)startTimer{
    __block NSInteger count = 0;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%ld",(long)count++);
        NSLog(@"Timer的currentModel=%@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
}

Created this code, but found that the Timer didn't execute at all. What is this for? The reason is because the NSRunLoop of the main thread is created by the system itself, so we don't need to create it, but the child thread we created does not have NSRunLoop, so we need to create it manually. add this line of code[[NSRunLoop currentRunLoop] run];

- (void)viewDidLoad {
    [super viewDidLoad];
    //开启线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimer) object:nil];
    [thread start];
    [self.view addSubview:self.tv_list];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

}
-(void)startTimer{
    __block NSInteger count = 0;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%ld",(long)count++);
        NSLog(@"Timer的currentModel=%@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
    //创建RunLoop
    [[NSRunLoop currentRunLoop] run];
}

In this way, NSTimer runs normally.

Conclusion: So after the above time, it can be concluded that NSTimer cannot work when UITableView slides because NSTimer relies on RunLoop for its work, and UITableView's sliding response also relies on RunLoop, but NSTimer NSDefaultRunLoopModetriggers the response in mode by default, while UITableView's sliding main The thread's RunLoop will switch to UITrackingRunLoopModemode, so NSTimer can't work when UITableView is sliding. The solution is relatively simple to add NSRunLoopCommonModes pseudo mode to NSTimer or directly add UITrackingRunLoopMode mode. In this article, it is mentioned to create a sub-thread and add RunLoop to the sub-thread to solve the problem of NSTimer sliding timing. The author does not recommend this method here, because the problem of circular reference is easy to occur in actual development. This method is only for the purpose of To test the RunLoop of the child thread, there is no need to superfluous to solve this simple problem.

Guess you like

Origin juejin.im/post/7116430251638390815