NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%ld",(long)count++);
}];
[self.view addSubview:self.tv_list];
J'en ai écrit un UITableView
, puis j'en ai ajouté un NSTimer
, que s'est-il passé ensuite ? Lorsque vous balayez UITableView
, le NSTimer
chronomètre s'arrête. Je crois que la plupart des développeurs ont rencontré ce problème.Bien sûr, Baidu aura de nombreuses réponses.
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
L'ajout de ce code résout parfaitement le problème, mais pourquoi cela se produit-il ? La supposition doit être liée à NSRunLoop. Alors, qu'est-ce que NSRunLoop ?
1. Qu'est-ce que NSRunLoop ?
NSRunLoop
NSPort
Les objets gèrent les entrées des sources, telles que les événements de souris et de clavier du système de fenêtres et des objets.NSRunLoop
Les objets gèrent également lesNSTimer
événements.Votre application ne crée ni ne gère explicitement d'
NSRunLoop
objets.NSThread
Le système crée un objet par objet selon les besoinsNSRunLoop
, y compris le thread principal de l'application. Si vous avez besoin d'accéder à la boucle d'exécution du thread actuel, utilisez une méthode de classecurrentRunLoop
.Notez que les objets ne sont pas des "entrées" du
NSRunLoop
point de vueNSTimer
- ils sont d'un type spécial, et lorsqu'ils déclenchent la boucle d'exécution, ils ne provoquent pas le retour de la boucle d'exécution.
Je n'arrive pas à comprendre ce que cela signifie. Si c'est facile à comprendre, cette chose est une boucle infinie.Lorsqu'il y a une réponse à un événement, RunLoop peut répondre et traiter certains événements.Lorsqu'il n'y a pas de réponse à un événement, RunLoop s'endormira, et cela correspond au thread un -à une. Un thread ne peut avoir qu'une seule RunLoop s'il y en a.
2. Qu'est-ce que NSRunLoopMode ?
Modes dans lesquels une boucle d'exécution fonctionne. Veuillez le traduire vous-même.
À l'heure actuelle, il existe cinq types de NSRunLoopMode, mais seuls NSDefaultRunLoopMode
UITrackingRunLoopModel
NSRunLoopCommonModes
ces trois sont utilisés sur iOS, et les deux autres sont utilisés sur MacOS.
NSDefaultRunLoopMode
Définissez le mode pour gérer les sources d'entrée autres que les objets de connexion.
UITrackingRunLoopModel
Le mode à définir lors du suivi dans le contrôle.
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]);
}];
}
A créé ce code, mais a constaté que le minuteur ne s'exécutait pas du tout. À quoi ça sert? La raison en est que le NSRunLoop du thread principal est créé par le système lui-même, nous n'avons donc pas besoin de le créer, mais le thread enfant que nous avons créé n'a pas de NSRunLoop, nous devons donc le créer manuellement. ajouter cette ligne de 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];
}
De cette manière, NSTimer s'exécute normalement.
Conclusion: Donc, après le temps ci-dessus, on peut conclure que NSTimer ne peut pas fonctionner lorsque UITableView glisse car NSTimer s'appuie sur RunLoop pour son travail, et la réponse glissante de UITableView s'appuie également sur RunLoop, mais NSTimer NSDefaultRunLoopMode
déclenche la réponse en mode par défaut, tandis que le glissement de UITableView principal Le RunLoop du thread passera en UITrackingRunLoopMode
mode, de sorte que NSTimer ne peut pas fonctionner lorsque UITableView glisse.La solution est relativement simple pour ajouter le pseudo-mode NSRunLoopCommonModes à NSTimer ou ajouter directement le mode UITrackingRunLoopMode. Dans cet article, il est mentionné de créer un sous-thread et d'ajouter RunLoop au sous-thread pour résoudre le problème de synchronisation de glissement de NSTimer. L'auteur ne recommande pas cette méthode ici, car le problème de référence circulaire est facile à se produire dans développement réel. Cette méthode est uniquement dans le but de tester la RunLoop du thread enfant, il n'y a pas besoin de superflu pour résoudre ce problème simple.