Подтвердить что ты не робот

Повторение NSTimer, слабая ссылка, владеющая ссылкой или iVar?

Я подумал, что я бы сказал, что это отдельный вопрос из моего предыдущего retaining-repeating-nstimer-for-later-access, поскольку обсуждение продвинулось вперед, сделав новый вопрос более ясным, чем еще один EDIT:

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

NSTimer *ti = [NSTimer scheduledTimerWithTimeInterval:1 
                                               target:self 
                                             selector:@selector(updateDisplay:) 
                                             userInfo:nil 
                                              repeats:YES];

Я понимаю, что при создании runloop становится владельцем NSTimer и в конечном итоге останавливается, удаляет и освобождает NSTimer при вызове [ti invalidate];.

В силу того, что нам нужно получить доступ к NSTimer более чем в одном методе, нам нужно каким-то образом сохранить ссылку для использования в будущем, пересмотренный вопрос:

// (1) Should the NSTimer be held using an owning reference (i.e.)
@property(nonatomic, retain) NSTimer *walkTimer;
[self setWalkTimer: ti];
...
...
// Cancel method
[[self walkTimer] invalidate;
[self setWalkTimer:nil];
...
...
// dealloc method
[walkTimer release];
[super dealloc];

.

// (2) Should the NSTimer be held using a weak reference (i.e.)
@property(nonatomic, assign) NSTimer *walkTimer;
[self setWalkTimer: ti];
...
...
// Cancel method
[[self walkTimer] invalidate];
[self setWalkTimer:nil];
...
...
// dealloc method
[super dealloc];

.

// (3) Use an iVar and rely on the runLoop holding (i.e. retaining) the timer
NSTimer *walkTimer;
NSTimer *walkTimer = [NSTimer scheduledTimerWithTimeInterval:1 
                                                      target:self 
                                                    selector:@selector(updateDisplay:) 
                                                    userInfo:nil 
                                                     repeats:YES];
...
...
// Cancel method
[walkTimer invalidate];
walkTimer = nil;

.

// (4) Something not listed above ...

Я рад за просто (1) (2) (3) или (4), поскольку много обсуждений, которые лучше всего написаны на Other поток. Кажется, что есть много противоречивых ответов, поэтому я надеюсь, что этот более конкретный вопрос поможет сосредоточиться на том, что может быть лучшей практикой в ​​этой ситуации.


EDIT:

В качестве дополнительной заметки в Ссылка на класс Apple NSTimer 4 из 5 примеров проектов кода используют NSTimers, которые назначаются ** на сохраняемое имущество. Вот пример того, что показывают ссылочные примеры классов:

@property (nonatomic, retain) NSTimer *updateTimer;
updateTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:@selector(updateCurrentTime) userInfo:p repeats:YES];
...
...
// Cancel
[updateTimer invalidate];
updateTimer = nil;
...
...
// Dealloc method
[super dealloc];
[updateTimer release];

** Следует отметить, что в примерах Apple назначают iVar напрямую и не используют средство настройки свойств.

4b9b3361

Ответ 1

Обдумав все это и обнаружив важный недостаток в моих рассуждениях, я пришел к другому выводу:

Неважно, держите ли вы принадлежащую или не принадлежащую ссылку на таймер, который нужно аннулировать. Это полностью вопрос вкуса.

Прерыватель сделки заключается в том, какова цель таймера:

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

Позвольте мне проиллюстрировать ситуацию с парой графов объектов:

  1. Вы начинаете в состоянии, из которого вы устанавливаете таймер и устанавливаете себя в качестве цели. Настройка таймера: yourObject принадлежит someClientObject. Параллельно существует текущий цикл выполнения с массивом scheduleTimers. метод setupTimer вызывается для yourObject:

  2. Результатом является следующее начальное состояние. В дополнение к прежнему состоянию у yourObject теперь есть ссылка (принадлежит или нет) на workTimer, который, в свою очередь, владеет yourObject. Кроме того, workTimer принадлежит workTimer scheduleTimers:

  3. Итак, теперь вы будете использовать объект, но когда вы закончите с ним и просто освободите его, вы получите простую утечку релиза: после того, как someClientObject от yourObject через простой релиз, yourObject объект yourObject от графа объекта. но поддерживал workTimer. workTimer и yourObject пропущены!

Где вы пропускаете объект (и таймер), потому что runloop поддерживает работу таймера, который, в свою очередь, сохраняет ссылку на ваш объект.

Этого можно избежать, если yourObject только когда - либо принадлежит одной инстанции в то время, когда он должным образом утилизированы надлежащей утилизации путем списания: перед утилизацией yourObject в связи с освобождением, someClientObject вызывает cancelTimer метод на yourObject. В этом методе yourObject делает недействительным workTimer и (если он владел workTimer) избавляется от workTimer через release:

Но теперь, как вы решаете следующую ситуацию?
Несколько владельцев: установка как в начальном состоянии, но теперь с несколькими независимыми clientObjects которые содержат ссылки на yourObject

Нет простого ответа, я в курсе! (Не то чтобы последний много говорил, но...)

Так что мой совет...

  1. Не делайте свой таймер собственностью/не предоставляйте доступ к нему! Вместо этого, держите его в ivar (с современной средой исполнения, я думаю, вы можете зайти так далеко, чтобы определить ivar в расширении класса) и работать с ним только из одного единственного объекта. (Вы можете сохранить его, если вам удобнее, но в этом нет никакой необходимости.)

    • Предостережение: если вам абсолютно необходим доступ к таймеру из другого объекта, сделайте так, чтобы свойство retain таймер (так как это единственный способ избежать сбоев, вызванных клиентами, которые напрямую аннулировали таймер, к которому они обращались), и предоставьте свой собственный установщик. Перенастройка таймера, на мой взгляд, не очень хорошая причина нарушать инкапсуляцию: предоставьте мутатор, если вам нужно это сделать.
  2. Установите таймер с целью, отличной от себя. (Существует множество способов сделать это. Может быть, путем написания универсального класса TimerTarget или - если вы можете его использовать - через MAZeroingWeakReference?)

Я прошу прощения за то, что был дебилом в первой дискуссии, и хочу поблагодарить Дэниела Dickison и Роба Нейпира за их терпение.

Так вот, теперь я буду работать с таймерами:

// NSTimer+D12WeakTimerTarget.h:
#import <Foundation/NSTimer.h>
@interface NSTimer (D12WeakTimerTarget)
+(NSTimer *)D12scheduledTimerWithTimeInterval:(NSTimeInterval)ti weakTarget:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat logsDeallocation:(BOOL)shouldLogDealloc;
@end

// NSTimer+D12WeakTimerTarget.m:
#import "NSTimer+D12WeakTimerTarget.h"
@interface D12WeakTimerTarget : NSObject {
    __weak id weakTarget;
    SEL selector;
    // for logging purposes:
    BOOL logging;
    NSString *targetDescription;
}
-(id)initWithTarget:(id)target selector:(SEL)aSelector shouldLog:(BOOL)shouldLogDealloc;
-(void)passthroughFiredTimer:(NSTimer *)aTimer;
-(void)dumbCallbackTimer:(NSTimer *)aTimer;
@end

@implementation D12WeakTimerTarget
-(id)initWithTarget:(id)target selector:(SEL)aSelector shouldLog:(BOOL)shouldLogDealloc
{
    self = [super init];
    if ( !self )
        return nil;

    logging = shouldLogDealloc;

    if (logging)
        targetDescription = [[target description] copy];

    weakTarget = target;
    selector = aSelector;

    return self;
}

-(void)dealloc
{
    if (logging)
        NSLog(@"-[%@ dealloc]! (Target was %@)", self, targetDescription);

    [targetDescription release];
    [super dealloc];
}

-(void)passthroughFiredTimer:(NSTimer *)aTimer;
{
    [weakTarget performSelector:selector withObject:aTimer];
}

-(void)dumbCallbackTimer:(NSTimer *)aTimer;
{
    [weakTarget performSelector:selector];
}
@end

@implementation NSTimer (D12WeakTimerTarget)
+(NSTimer *)D12scheduledTimerWithTimeInterval:(NSTimeInterval)ti weakTarget:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat logsDeallocation:(BOOL)shouldLogDealloc
{
    SEL actualSelector = @selector(dumbCallbackTimer:);
    if ( 2 != [[target methodSignatureForSelector:aSelector] numberOfArguments] )
        actualSelector = @selector(passthroughFiredTimer:);

    D12WeakTimerTarget *indirector = [[D12WeakTimerTarget alloc] initWithTarget:target selector:selector shouldLog:shouldLogDealloc];

    NSTimer *theTimer = [NSTimer scheduledTimerWithTimeInterval:ti target:indirector selector:actualSelector userInfo:userInfo repeats:shouldRepeat];
    [indirector release];

    return theTimer;
}
@end

Оригинал (для полного раскрытия):

Вы знаете мое мнение из вашего другого поста:

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

Тем не менее, ваши варианты 2 и 3 по сути одинаковы. (В [self setWalkTimer:nil] есть дополнительная [self setWalkTimer:nil] сообщений [self setWalkTimer:nil] walkTimer = nil но я не уверен, что компилятор не оптимизирует это и не получит прямой доступ к ivar, но хорошо...)

Ответ 2

Обычно я управляю недействительным внутри аксессора, чтобы вы никогда не удивлялись, когда таймер обратился к вам после того, как вы считаете, что избавились от него:

@property(nonatomic, retain) NSTimer *walkTimer;
[self setWalkTimer: ti];

- (void)setWalkTimer:(NSTimer *)aTimer
{
    if (aTimer != walkTimer_)
    {
        [aTimer retain];
        [walkTimer invalidate];
        [walkTimer release];
        walkTimer = aTimer;
    }
}
...
...
// Cancel method
[self setWalkTimer:nil];
...
...
// Make a new timer, automatically invalidating the old one
[self setWalkTimer:[... a new timer ...]]
...
...
// dealloc method
[walkTimer_ invalidate];
[walkTimer_ release];
[super dealloc];