1. Première expérience de RunLoop~Thinking

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 NSTimerchronomè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 ?

Capture d'écran 2022-07-04 14.21.09.png

NSRunLoopNSPortLes 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. NSRunLoopLes objets gèrent également les NSTimerévénements.

Votre application ne crée ni ne gère explicitement d' NSRunLoopobjets. NSThreadLe système crée un objet par objet selon les besoins NSRunLoop, 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 classe currentRunLoop.

Notez que les objets ne sont pas des "entrées" du NSRunLooppoint de vue NSTimer- 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 ?

Capture d'écran 2022-07-04 14.50.55.png

Modes dans lesquels une boucle d'exécution fonctionne. Veuillez le traduire vous-même.

Capture d'écran 2022-07-04 15.00.32.png

À l'heure actuelle, il existe cinq types de NSRunLoopMode, mais seuls NSDefaultRunLoopMode UITrackingRunLoopModel NSRunLoopCommonModesces trois sont utilisés sur iOS, et les deux autres sont utilisés sur MacOS.

NSDefaultRunLoopMode

Capture d'écran 2022-07-04 15.05.02.png

Définissez le mode pour gérer les sources d'entrée autres que les objets de connexion.

UITrackingRunLoopModel

Capture d'écran 2022-07-04 15.05.56.png

Le mode à définir lors du suivi dans le contrôle.

NSRunLoopCommonModes

Capture d'écran 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]);
    }];
}

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 NSDefaultRunLoopModedéclenche la réponse en mode par défaut, tandis que le glissement de UITableView principal Le RunLoop du thread passera en UITrackingRunLoopModemode, 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.

Je suppose que tu aimes

Origine juejin.im/post/7116430251638390815
conseillé
Classement