1. Erste Erfahrungen mit RunLoop~Thinking

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

[self.view addSubview:self.tv_list];

Ich habe einen geschrieben UITableViewund dann einen hinzugefügt NSTimer, was ist dann passiert? Beim Wischen stoppt UITableViewder NSTimerTimer. Ich glaube, dass die meisten Entwickler auf dieses Problem gestoßen sind, und Baidu wird natürlich viele Antworten haben.

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

Das Hinzufügen dieses Codes löst das Problem perfekt, aber warum passiert das? Die Vermutung muss sich auf NSRunLoop beziehen. Was ist NSRunLoop?

1. Was ist NSRunLoop?

Screenshot 2022-07-04 14.21.09.png

NSRunLoopNSPortObjekte verarbeiten Eingaben von Quellen, wie Maus- und Tastaturereignisse aus dem Fenstersystem und Objekten. NSRunLoopObjekte behandeln auch NSTimerEreignisse.

Ihre Anwendung erstellt weder Objekte, noch verwaltet sie explizit NSRunLoopObjekte. NSThreadDas System erstellt nach Bedarf ein Objekt pro Objekt NSRunLoop, einschließlich des Hauptthreads der Anwendung. Wenn Sie Zugriff auf die Ausführungsschleife des aktuellen Threads benötigen, verwenden Sie eine Klassenmethode currentRunLoop.

Beachten Sie, dass Objekte aus NSRunLoopder Sicht keine "Eingaben" NSTimersind - sie sind ein spezieller Typ, und wenn sie die Run-Schleife auslösen, bewirken sie nicht, dass die Run-Schleife zurückkehrt.

Kann anscheinend nicht verstehen, was es bedeutet. Wenn es leicht zu verstehen ist, ist dieses Ding eine Endlosschleife. Wenn es eine Ereignisantwort gibt, kann RunLoop auf einige Ereignisse reagieren und diese verarbeiten. Wenn es keine Ereignisantwort gibt, geht RunLoop in den Ruhezustand und entspricht dem Thread -zu eins. Ein Thread kann nur einen RunLoop haben, falls vorhanden.

2. Was ist NSRunLoopMode?

Screenshot 2022-07-04 14.50.55.png

Modi, in denen eine Laufschleife arbeitet. Bitte übersetzen Sie es selbst.

Screenshot 2022-07-04 15.00.32.png

Derzeit gibt es fünf Arten von NSRunLoopMode, aber nur NSDefaultRunLoopMode UITrackingRunLoopModel NSRunLoopCommonModesdiese drei werden unter iOS verwendet, und die verbleibenden zwei werden unter MacOS verwendet.

NSDefaultRunLoopMode

Screenshot 2022-07-04 15.05.02.png

Stellen Sie den Modus so ein, dass andere Eingabequellen als Verbindungsobjekte verarbeitet werden.

UITrackingRunLoopModel

Screenshot 2022-07-04 15.05.56.png

Der einzustellende Modus beim Verfolgen in der Steuerung.

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]);
    }];
}

Diesen Code erstellt, aber festgestellt, dass der Timer überhaupt nicht ausgeführt wurde. Wofür ist das? Der Grund dafür ist, dass der NSRunLoop des Haupt-Threads vom System selbst erstellt wird, sodass wir ihn nicht erstellen müssen, aber der von uns erstellte untergeordnete Thread hat keinen NSRunLoop, sodass wir ihn manuell erstellen müssen. füge diese Codezeile hinzu[[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];
}

Auf diese Weise läuft NSTimer normal.

Fazit: Nach der oben genannten Zeit kann also geschlussfolgert werden, dass NSTimer nicht funktionieren kann, wenn UITableView gleitet, da NSTimer für seine Arbeit auf RunLoop angewiesen ist, und die gleitende Antwort von UITableView auch auf RunLoop angewiesen ist, aber NSTimer NSDefaultRunLoopModedie Antwort standardmäßig im Modus auslöst, während UITableView gleitet main Der RunLoop des Threads wechselt in den UITrackingRunLoopModeModus, sodass NTimer nicht funktionieren kann, wenn UITableView gleitet.Die Lösung ist relativ einfach, NSRunLoopCommonModes-Pseudomodus zu NSRunLoopCommonModes hinzuzufügen oder direkt den UITrackingRunLoopMode-Modus hinzuzufügen. In diesem Artikel wird erwähnt, einen Sub-Thread zu erstellen und RunLoop zum Sub-Thread hinzuzufügen, um das Problem des gleitenden Timings von NTimer zu lösen. Der Autor empfiehlt diese Methode hier nicht, da das Problem des Zirkelbezugs leicht auftritt Diese Methode dient nur dem Zweck, den RunLoop des untergeordneten Threads zu testen, es besteht keine Notwendigkeit, dieses einfache Problem zu lösen.

Ich denke du magst

Origin juejin.im/post/7116430251638390815
Empfohlen
Rangfolge