Используя таймеры

Существует несколько аспектов к использованию таймера. При создании таймера необходимо сконфигурировать его так, чтобы он знал, какое сообщение отправить, к какой объект, когда он стреляет. Необходимо тогда связать его с циклом выполнения так, чтобы это стреляло — некоторые методы создания делают это для Вас автоматически. Наконец при создании повторяющегося таймера необходимо лишить законной силы его, когда Вы хотите, чтобы он прекратил стрелять.

Создание и планирование таймера

Существуют, вообще говоря, три способа создать таймер:

  1. Планирование таймера с текущим циклом выполнения;

  2. Создание таймера, который Вы позже регистрируете в цикле выполнения;

  3. Инициализация таймера с данной датой огня.

Во всех случаях необходимо сконфигурировать таймер для сообщения его, что обменивается сообщениями, это должно отправить к тому, какой объект, когда это стреляет, и должно ли это повториться. С некоторыми методами можно также обеспечить пользовательский информационный словарь. Можно поместить то, что Вы хотите в этот словарь, который может быть полезным в методе, который вызывает таймер, когда это стреляет.

Существует два способа сказать таймер, что обменивается сообщениями, он должен отправить и объект, в который он должен отправить сообщение — путем указания каждого независимо, или (в некоторых случаях) при помощи экземпляра NSInvocation. При указании селектора для сообщения непосредственно имя метода не имеет значения, но это должно иметь следующую подпись:

- (void)targetMethod:(NSTimer*)theTimer

При создании объекта вызова можно указать любое сообщение, которое Вы хотите. (Для больше об объектах вызова, посмотрите Используя NSInvocation в Распределенных Объектах, Программируя Темы.)

Ссылки на таймеры и объектные времена жизни

Поскольку цикл выполнения поддерживает таймер, с точки зрения объектных времен жизни обычно нет никакой потребности сохранить ссылку на таймер после планирования его. (Поскольку таймер передается как параметр, когда Вы указываете его метод как селектор, можно лишить законной силы повторяющийся таймер в надлежащих случаях в том методе.) Во многих ситуациях, однако, Вы также хотите опцию лишения законной силы таймера — возможно, даже, прежде чем это запустится. В этом случае действительно необходимо сохранить ссылку на таймер, так, чтобы можно было остановить его если это уместно. При создании незапланированного таймера (см. Незапланированные Таймеры), то необходимо поддержать сильную ссылку к таймеру так, чтобы это не было освобождено перед использованием его.

Таймер поддерживает сильную ссылку к своей цели. Это означает, что, пока таймер остается допустимым, его цель не будет освобождена. Как заключение, это означает, что оно не целесообразно для цели таймера пытаться лишить законной силы таймер в dealloc метод — dealloc метод не будет вызван, пока таймер допустим.

Примеры таймера

Для примеров, следующих, рассмотрите объект контроллера таймера, объявляющий, что методы запускают и (в некоторых случаях) останавливают четыре таймера, сконфигурированные по-разному. Это имеет свойства для двух из таймеров; свойство для подсчета, сколько раз один из таймеров запустил, и три связанных с таймером метода (targetMethod:, invocationMethod:, и countedTimerFireMethod:). Контроллер также обеспечивает метод для предоставления пользовательского информационного словаря.

@interface TimerController : NSObject
 
// The repeating timer is a weak property.
@property (weak) NSTimer *repeatingTimer;
@property (strong) NSTimer *unregisteredTimer;
@property NSUInteger timerCount;
 
- (IBAction)startOneOffTimer:sender;
 
- (IBAction)startRepeatingTimer:sender;
- (IBAction)stopRepeatingTimer:sender;
 
- (IBAction)createUnregisteredTimer:sender;
- (IBAction)startUnregisteredTimer:sender;
- (IBAction)stopUnregisteredTimer:sender;
 
- (IBAction)startFireDateTimer:sender;
 
- (void)targetMethod:(NSTimer*)theTimer;
- (void)invocationMethod:(NSDate *)date;
- (void)countedTimerFireMethod:(NSTimer*)theTimer;
 
- (NSDictionary *)userInfo;
 
@end

Реализации пользовательского информационного метода и два из методов, вызванных таймерами, могли бы быть следующим образом (countedTimerFireMethod: описан в Остановке Таймера):

- (NSDictionary *)userInfo {
    return @{ @"StartDate" : [NSDate date] };
}
 
- (void)targetMethod:(NSTimer*)theTimer {
    NSDate *startDate = [[theTimer userInfo] objectForKey:@"StartDate"];
    NSLog(@"Timer started on %@", startDate);
}
 
- (void)invocationMethod:(NSDate *)date {
    NSLog(@"Invocation for timer started on %@", date);
}

Запланированные таймеры

Следующие два метода класса автоматически регистрируют новый таймер в токе NSRunLoop объект в режиме по умолчанию (NSDefaultRunLoopMode):

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

- (IBAction)startOneOffTimer:sender {
 
    [NSTimer scheduledTimerWithTimeInterval:2.0
             target:self
             selector:@selector(targetMethod:)
             userInfo:[self userInfo]
             repeats:NO];
}

Таймер автоматически запущен циклом выполнения после 2 секунд и тогда удален из цикла выполнения.

Следующий пример показывает, как можно запланировать повторяющийся таймер, снова использующий селектор (аннулирование описано в Остановке Таймера):

- (IBAction)startRepeatingTimer:sender {
 
    // Cancel a preexisting timer.
    [self.repeatingTimer invalidate];
 
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5
                              target:self selector:@selector(targetMethod:)
                              userInfo:[self userInfo] repeats:YES];
    self.repeatingTimer = timer;
}

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

Незапланированные таймеры

Следующие методы создают таймеры, которые можно запланировать в более позднее время путем отправки сообщения addTimer:forMode: к NSRunLoop объект.

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

- (IBAction)createUnregisteredTimer:sender {
 
    NSMethodSignature *methodSignature = [self methodSignatureForSelector:@selector(invocationMethod:)];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    [invocation setTarget:self];
    [invocation setSelector:@selector(invocationMethod:)];
    NSDate *startDate = [NSDate date];
    [invocation setArgument:&startDate atIndex:2];
 
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 invocation:invocation repeats:YES];
    self.unregisteredTimer = timer;
}
 
- (IBAction)startUnregisteredTimer:sender {
 
    if (self.unregisteredTimer != nil) {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addTimer:self.unregisteredTimer forMode:NSDefaultRunLoopMode];
    }
}

Инициализация таймера с датой огня

Можно выделить NSTimer возразите себе и отправьте его initWithFireDate:interval:target:selector:userInfo:repeats: сообщение. Это позволяет Вам указывать начальную дату огня независимо от повторного интервала. Как только Вы создали таймер, единственное свойство, которое можно изменить, является своей датой увольнения (использование setFireDate:). Все другие параметры являются неизменными после создания таймера. Чтобы заставить таймер начинать стрелять, необходимо добавить его к циклу выполнения.

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

- (IBAction)startFireDateTimer:sender {
 
    NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
    NSTimer *timer = [[NSTimer alloc] initWithFireDate:fireDate
                                      interval:0.5
                                      target:self
                                      selector:@selector(countedTimerFireMethod:)
                                      userInfo:[self userInfo]
                                      repeats:YES];
 
    self.timerCount = 1;
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
}

В этом примере, несмотря на то, что таймер сконфигурирован для повторения, он будет остановлен после того, как он стрелял три раза countedTimerFireMethod: то, что это вызывает — посмотрите Остановку Таймера.

Остановка таймера

При создании неповторяющегося таймера нет никакой потребности принять дальнейшие меры. Это автоматически останавливает себя после того, как это будет стрелять. Например, нет никакой потребности остановить таймер, создаваемый в Инициализации Таймера с Датой Огня. При создании повторяющегося таймера, однако, Вы останавливаете его путем отправки его invalidate сообщение. Можно также отправить неповторяющийся таймер invalidate сообщение, прежде чем это будет стрелять, чтобы препятствовать тому, чтобы он стрелял.

Следующие примеры показывают методы остановки для таймеров, создаваемых в предыдущих примерах:

- (IBAction)stopRepeatingTimer:sender {
    [self.repeatingTimer invalidate];
    self.repeatingTimer = nil;
}
 
- (IBAction)stopUnregisteredTimer:sender {
    [self.unregisteredTimer invalidate];
    self.unregisteredTimer = nil;
}

Можно также лишить законной силы таймер от метода, который он вызывает. Например, метод, вызванный таймером, показанным в Инициализации Таймера с Датой Огня, мог бы быть похожим на это:

- (void)countedTimerFireMethod:(NSTimer*)theTimer {
 
    NSDate *startDate = [[theTimer userInfo] objectForKey:@"StartDate"];
    NSLog(@"Timer started on %@; fire count %d", startDate, self.timerCount);
 
    self.timerCount++;
    if (self.timerCount > 3) {
        [theTimer invalidate];
    }
}

Это лишит законной силы таймер после того, как он будет стрелять три раза. Поскольку таймер передается как параметр методу, который он вызывает, не может быть никакой потребности поддержать таймер как переменную. Как правило, однако, Вы могли бы, тем не менее, сохранить ссылку на таймер в случае, если Вы хотите опцию остановки его ранее.