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 UITableView
und dann einen hinzugefügt NSTimer
, was ist dann passiert? Beim Wischen stoppt UITableView
der NSTimer
Timer. 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?
NSRunLoop
NSPort
Objekte verarbeiten Eingaben von Quellen, wie Maus- und Tastaturereignisse aus dem Fenstersystem und Objekten.NSRunLoop
Objekte behandeln auchNSTimer
Ereignisse.Ihre Anwendung erstellt weder Objekte, noch verwaltet sie explizit
NSRunLoop
Objekte.NSThread
Das System erstellt nach Bedarf ein Objekt pro ObjektNSRunLoop
, einschließlich des Hauptthreads der Anwendung. Wenn Sie Zugriff auf die Ausführungsschleife des aktuellen Threads benötigen, verwenden Sie eine KlassenmethodecurrentRunLoop
.Beachten Sie, dass Objekte aus
NSRunLoop
der Sicht keine "Eingaben"NSTimer
sind - 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?
Modi, in denen eine Laufschleife arbeitet. Bitte übersetzen Sie es selbst.
Derzeit gibt es fünf Arten von NSRunLoopMode, aber nur NSDefaultRunLoopMode
UITrackingRunLoopModel
NSRunLoopCommonModes
diese drei werden unter iOS verwendet, und die verbleibenden zwei werden unter MacOS verwendet.
NSDefaultRunLoopMode
Stellen Sie den Modus so ein, dass andere Eingabequellen als Verbindungsobjekte verarbeitet werden.
UITrackingRunLoopModel
Der einzustellende Modus beim Verfolgen in der Steuerung.
NSRunLoopCommonModes
包含一个或多个其他运行循环模式的伪模式。
其实这个伪模式中包含了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 NSDefaultRunLoopMode
die Antwort standardmäßig im Modus auslöst, während UITableView gleitet main Der RunLoop des Threads wechselt in den UITrackingRunLoopMode
Modus, 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.