6
votes

Précision de NSTIMER

J'essaie d'utiliser NSTIMER pour créer une minuterie de style d'arrêt qui incrémente toutes les 0,1 secondes, mais il semble courir trop vite parfois.

C'est comme ça que je l'ai fait: P> xxx pré>

et ensuite: p>

-(void)updateTimeLabel
{
    maxTime=maxTime+0.1;
    timerLabel.text =[NSString stringWithFormat:@"%.1f Seconds",maxTime];
}


10 commentaires

Dans quelle méthode crée-tu la minuterie?


Si vous êtes des événements de synchronisation, vous devez appeler cfabsoluTimeGetCurrent () lorsque vous démarrez votre minuterie, appelez-la à nouveau à la fin de l'intervalle. Soustrayez les 2 valeurs et vous obtiendrez le temps écoulé en quelques secondes.


C'est-à-dire que le temps réel écoulé est indépendant du temps que vous affichez. Vous pouvez donc mettre à jour votre écran environ tous les 0,1, mais cela n'a pas vraiment d'importance si vos mises à jour sont désactivées un peu. Ensuite, lorsque votre minuterie est en pause, mettez à nouveau la mise à jour de l'écran à l'aide de la méthode dans mon dernier commentaire.


De plus, si vous souhaitez que votre affichage met à jour au moins tous les 0,1, je voudrais mettre ma minuterie pour mettre à jour chaque 0,05.


Rob - la minuterie est créée dans le fichier * .h et initialisée avec ViewDidLoadLoad


Niels - Non, comme vous le voyez, je présente le temps en direct dans une étiquette


J'ai posté le code qui illustre ce que vous voulez.


Les répétitions doivent être assez précises si vous tenez la bouche droite.


"Le problème est qu'il fonctionne très précisément." -- C'est un problème?


@ user2308343 Vous dites que votre minuterie est initialisée dans ViewDidLoad . C'est OK. Mon point principal est que vous ne voulez pas essayer de invalider la minuterie dans dealloc . Vous faites généralement cela dans ViewWilldisappear . Et pour des raisons de symétrie, vous voudrez donc planifier la minuterie dans ViewDidAppear plutôt que ViewDidLoad . (Si vous, par exemple, appuyez sur une autre scène, vous obtiendrez Viewwilldisappear , mais lorsque vous revenez, ViewDidLoad pas est appelé Encore une fois, d'où le modèle commun pour le faire dans ViewWillappear .)


4 Réponses :


9
votes

Selon le NSTIMER < / code> documentation, il n'est pas censé être précis.

En raison des différentes sources d'entrée, une boucle d'exécution typique gère, la résolution effective de l'intervalle de temps pour une minuterie est limitée à l'ordre de 50-100 millisecondes. Si le temps de cuisson d'une minuterie se produit pendant une longue callait ou si la boucle d'exécution est en mode qui ne surveille pas la minuterie, la minuterie ne tire pas avant la prochaine fois que la boucle d'exécution vérifie la minuterie. Par conséquent, le temps réel à laquelle les tirs de la minuterie peuvent potentiellement être une période de temps significative après le temps de tir prévu.

Vous voudrez peut-être utiliser le DISPATTATCH_ADER fonction de GCD, qui est suggéré par le Documentation officielle Pour ce but exact (Création d'une minuterie).

Si vous souhaitez effectuer un bloc une fois après un intervalle de temps spécifié, vous pouvez utiliser le Dispatch_After ou DisPatch_After_f Fonction.


Au fait, je suis d'accord avec la réponse de Caleb. Vous allez probablement résoudre vos problèmes si vous n'accumulez pas d'erreur comme votre faire en ce moment. Si vous stockez la date de début et recalculez l'heure à chaque itération à l'aide du -imeInterveinceinceinceince: , vous allez retrouver avec une mise à jour de l'interface utilisateur précise, quelle que soit la précision de la minuterie.


4 commentaires

Gabriele - Je n'ai pas besoin de "temps-car depuis", car je dois afficher le temps continuellement à l'utilisateur dans une étiquette ...


Vous en avez besoin pour ne pas accumuler d'erreur. Si vous recompagnez le temps de chaque "itération", l'erreur n'est pas accumulée. Sinon, vous résumez chaque imprécision de Nstimer , finissant par une mesure très imprécise.


Nstimer n'est pas "imprécis" si vous utilisez une répétition.


@Hotlicks: Avez-vous une référence pour cette réclamation? Pourquoi l'option de répétition pourrait-elle nier l'accumulation d'erreur?



2
votes

NSTIMER n'est pas garanti d'être précis, bien que dans la pratique, c'est généralement (si vous ne faites rien d'autre sur votre fil principal ...). Cependant, il est parfaitement raisonnable de mettre à jour un affichage ... n'utilisez tout simplement pas le rappel pour calculer votre minuterie. Enregistrez l'heure actuelle lorsque vous démarrez votre minuterie et obtenez la différence entre maintenant et lorsque vous avez commencé chaque fois que la minuterie incendie. Ensuite, ce n'est pas vraiment important à quel point NSTimer est précisément la cuisson, cela n'a aucune incidence sur combien de fois une seconde que votre écran affichage à l'écran.


0 commentaires

4
votes
maxTime = [[NSDate date] timeIntervalSince:startDate];

2 commentaires

Mais je suppose que le déclencheur NSTIMER est la chose qui se passe toutes les 0,1 secondes, et c'est augmenter Maxtime - Maxtime suit la minuterie et ne le définit pas


Je comprends, mais la minuterie peut être éteinte de 50 ms d'une manière ou d'une autre. Ce n'est pas une forte erreur pour beaucoup de choses, mais cela va ajouter rapidement. Personne ne se soucie de la mise à jour de l'affichage exactement tous les 0,1 seconde, mais lorsque vous faire se déplacer pour mettre à jour l'affichage, il devrait être aussi précis que possible. Vous y réalisez cela en calculant en fonction de la différence entre l'heure actuelle et une heure de départ fixe, évitez donc l'accumulation d'erreur.



5
votes

Voici une classe que vous pouvez utiliser pour faire ce que vous voulez:

@interface StopWatch()
@property ( nonatomic, strong ) NSTimer * displayTimer ;
@property ( nonatomic ) CFAbsoluteTime startTime ;
@end

@implementation StopWatch

-(void)dealloc
{
    [ self.displayTimer invalidate ] ;
}

-(void)startTimer
{
    self.startTime = CFAbsoluteTimeGetCurrent() ;
    self.displayTimer = [ NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector( timerFired: ) userInfo:nil repeats:YES ] ;
}

-(void)stopTimer
{
    [ self.displayTimer invalidate ] ;
    self.displayTimer = nil ;

    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
    [ self updateDisplay:elapsedTime ] ;
}

-(void)timerFired:(NSTimer*)timer
{
    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
    [ self updateDisplay:elapsedTime ] ;
}

-(void)updateDisplay:(CFAbsoluteTime)elapsedTime
{
    // update your label here
}

@end


8 commentaires

Salut Niels ... semble fonctionner, mais l'étiquette affiche une très grande valeur, même si j'ai utilisé la valeur relative pour le temps écoulé ... Toute idée pourquoi?


+1 Mais quelques réflexions: 1. Vous ne pouvez pas invalider la minuterie dans dealloc , car la forte référence de la minuterie empêchera dealloc de toujours être appelé. Vous avez un cycle de référence fort ici. Vous devriez probablement créer la minuterie dans ViewWillappear et Invalidate dans ViewWilldisappear . 2. Je pourrais également suggérer un cadisplaylink plutôt qu'un NSTIMER , mais l'idée est la même.


En d'autres termes (comme j'ai compris) ... Je n'ai pas vraiment besoin de passer «écailles» à la méthode d'affichage, car il est viré avec précision maintenant? Mon UpdateTimeMabel initial fonctionnerait toujours depuis que la mise à jour est bien en utilisant la différence absolue dans TimerFired.


@ user2308343 Je pense que le message à emporter de la réponse de Nielsbot est que vous ne devriez pas maintenir votre propre comptoir. Vous ne devriez pas compter sur la précision des minuteries. Vous devez appeler votre minuterie avec une fréquence suffisamment élevée pour obtenir l'UX souhaité, mais calculer le temps écoulé en utilisant des différences entre le démarrage cfabsoluTimegetCurrent () et la valeur actuelle.


ce que @rob a dit. Aussi @rob: J'ai écrit un programme simple pour tester cela et -Dealloc est appelé. Je pense qu'utiliser cadisplaylink () pourrait être une surenvéralisation de la CPU, mais vous obtiendrez un affichage à l'écran plus précis, non?


@ user2308343 Vérifiez votre code d'affichage pour les bugs. Comment formatez-vous la valeur de l'heure à l'affichage? J'ai testé ce code et cela donne le résultat correct pour moi.


@nielsbot sur le cadisplayLink , deux observations: 1. Si vous utilisez nsrunloopcommonModes Vous pouvez continuer à fonctionner pendant que d'autres choses se passent. Par exemple, si vous faites défiler une tableView en même temps, la minuterie standard s'arrête jusqu'à ce que le défilement soit effectué. 2. Si vous êtes inquiet pour la charge de calcul, vous pouvez définir le cadre (code> sur une valeur plus élevée. Mais cela n'est pas appelé avant que ce soit prêt à redessiner l'écran, donc je ne serais donc pas inquiet pour les frais généraux de calcul. Il ne devrait éventuellement pas mettre à jour son étiquette si la chaîne de temps a changé, de toute façon (même si vous utilisez la minuterie).


@nielsbot sur le Invalidate Dans DealLoc , ce modèle d'essayer d'invalider une minuterie répétée dans DealLoc est un cycle de conservation commun. (La méthode DEALLOC est appelée lorsqu'il n'y a pas de références plus fortes, essayez donc de résoudre la référence de la minuterie dans une méthode dealloc T appelé jusqu'à ce qu'il n'y ait plus de références plus fortes ne fonctionnera pas.) Dans mon test, DealLoc n'est pas appelé. (Ne me trompe pas. Sinon j'aime bien votre réponse.)