Повторяя NSTimer, слабая ссылка, владеющая ссылкой или iVar? - PullRequest
10 голосов
/ 09 февраля 2011

Я подумал, что вынесу это здесь в качестве отдельного вопроса от моего предыдущего сохраняющего-повторяющегося-nstimer-for-позднее-доступа , поскольку обсуждение продвинулось вперед, делая новый вопрос более понятным, чем еще один 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 .Кажется, что есть много противоречивых ответов, поэтому я надеюсь, что этот более конкретный вопрос поможет сосредоточиться на том, что может быть лучшей практикой в ​​этой ситуации.


РЕДАКТИРОВАТЬ:

В качестве стороныобратите внимание, что в Apple NSTimer Class Reference 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 напрямую и не использует установщик свойств.

Ответы [ 2 ]

18 голосов
/ 09 февраля 2011

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Не делайте свой таймер собственностью / донане предоставлю аксессуаров для него!Вместо этого, держите его в секрете (с современной средой выполнения, которую я думаю , вы можете зайти так далеко, чтобы определить 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] вместо walkTimer = nil задействованы дополнительные сообщения, но я не уверен, что компилятор не оптимизирует это и не получит прямой доступ к ivar, но хорошо ...)

2 голосов
/ 10 февраля 2011

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

@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];
...