Метод действия NSTimer не вызывается - PullRequest
0 голосов
/ 21 ноября 2011

У меня есть следующий код для создания NSTimer, который должен обновлять метку при каждом запуске:

.h файл

@interface Game : UIViewController
{
    NSTimer *updateTimer;

    UILabel *testLabel;
    int i;
}
-(void)GameUpdate;

@end

.m файл

@implementation Game

-(void)GameUpdate
{
    i++;
    NSString *textToDisplay = [[NSString alloc] initWithFormat:@"Frame: %d", i];
    [testLabel setText:textToDisplay];
    NSLog(@"Game Updated");
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
    updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];
}

//other methods (viewDidUnload, init method, etc.)

@end

Когда я запускаю его, в верхней части появляется метка с надписью «0», но она не меняется. Это заставляет меня поверить, что я что-то упустил в настройке NSTimer. Что я пропустил?

Я использовал точки останова и (как вы можете видеть) ведение журнала, чтобы увидеть, действительно ли работает метод, а не какую-то другую ошибку.

Ответы [ 4 ]

14 голосов
/ 28 февраля 2013

У меня была похожая проблема, и у нее была другая основная причина, связанная с циклом выполнения.Стоит отметить, что при планировании таймера с кодом:

updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];

Таймер будет запланирован с использованием текущего цикла current потока.В вашем случае, потому что вы делаете этот вызов в viewDidLoad, это основной поток, так что вы готовы идти.

Однако, если вы планируете свой таймер с потоком, отличным от основного потока, онбудет запланировано в runLoop для этого потока, а не основного.Это нормально, но во вспомогательных потоках вы несете ответственность за создание и запуск начального цикла выполнения, поэтому, если вы этого не сделали, ваш обратный вызов никогда не будет вызван.

Решение состоит в том, чтобы либо запуститьrunLoop для вашего вспомогательного потока или для отправки запуска по таймеру в основной поток.

для отправки:

dispatch_async(dispatch_get_main_queue(), ^{
    updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];
});

Чтобы запустить цикл запуска:

После создания потокаиспользуя выбранный вами API, вызовите CFRunLoopGetCurrent (), чтобы выделить начальный цикл выполнения для этого потока.Любые будущие вызовы CFRunLoopGetCurrent будут возвращать тот же цикл выполнения.

CFRunLoopGetCurrent();
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];
7 голосов
/ 21 ноября 2011

Ваш обратный звонок должен иметь эту подпись:

-(void)GameUpdate:(NSTimer *)timer

Это явно в документах. И ссылка @selector () при настройке таймера должна быть @selector(GameUpdate:) (обратите внимание на конечный :).

Попробуй это.

1 голос
/ 18 октября 2012

На всякий случай, если кто-нибудь наткнется на это, я хочу отметить, что:

[[NSString alloc] initWithFormat:@"Frame: %d", i];

Требуется управление памятью.

Безопасно замените на:

[NSString stringWithFormat:@"Frame: %d", i];

для того же эффекта, но не нужно управление памятью.

PS На момент написания статьи я не могу комментировать исходное сообщение, поэтому я добавил это как ответ.

РЕДАКТИРОВАТЬ: Как Адам Уэйт , как указано ниже, на самом деле это больше не актуально при широком использовании ARC.

0 голосов
/ 21 октября 2016

У меня была немного другая проблема с NSTimer - запланированный вызов метода был проигнорирован при прокрутке UITableView. Таймер был запущен из основного потока. Явное добавление таймера в основной цикл выполнения решило проблему.

[[NSRunLoop mainRunLoop] addTimer:playbackTimer forMode:NSRunLoopCommonModes];

Решение найдено здесь https://stackoverflow.com/a/2716605/1994889

UPD : CADisplayLink гораздо лучше подходит для обновления пользовательского интерфейса. Согласно официальной документации, CADisplayLink представляет собой:

Класс, представляющий таймер, привязанный к дисплею vsync.

И может быть легко реализовано как:

  playbackTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateUI)];
 [playbackTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

и удалены как

if (playbackTimer) {
    [playbackTimer removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    playbackTimer = nil;
}
...