Ответ 1
Обдумав все это и обнаружив важный недостаток в моих рассуждениях, я пришел к другому выводу:
Неважно, держите ли вы принадлежащую или не принадлежащую ссылку на таймер, который нужно аннулировать. Это полностью вопрос вкуса.
Прерыватель сделки заключается в том, какова цель таймера:
Если объект, который создает таймер, является его целью, управление временем жизни этого объекта становится более хрупким: его нельзя просто сохранить/отпустить, вместо этого необходимо убедиться, что клиент, который содержит последнюю ссылку на этот объект, делает его недействительным таймером до того, как это избавляет от этого.
Позвольте мне проиллюстрировать ситуацию с парой графов объектов:
-
Вы начинаете в состоянии, из которого вы устанавливаете таймер и устанавливаете себя в качестве цели. Настройка таймера:
yourObject
принадлежитsomeClientObject
. Параллельно существует текущий цикл выполнения с массивом scheduleTimers. метод setupTimer вызывается дляyourObject
: -
Результатом является следующее начальное состояние. В дополнение к прежнему состоянию у
yourObject
теперь есть ссылка (принадлежит или нет) наworkTimer
, который, в свою очередь, владеетyourObject
. Кроме того,workTimer
принадлежитworkTimer
scheduleTimers: -
Итак, теперь вы будете использовать объект, но когда вы закончите с ним и просто освободите его, вы получите простую утечку релиза: после того, как
someClientObject
отyourObject
через простой релиз,yourObject
объектyourObject
от графа объекта. но поддерживалworkTimer
.workTimer
иyourObject
пропущены!
Где вы пропускаете объект (и таймер), потому что runloop поддерживает работу таймера, который, в свою очередь, сохраняет ссылку на ваш объект.
Этого можно избежать, если yourObject
только когда - либо принадлежит одной инстанции в то время, когда он должным образом утилизированы надлежащей утилизации путем списания: перед утилизацией yourObject
в связи с освобождением, someClientObject
вызывает cancelTimer
метод на yourObject. В этом методе yourObject делает недействительным workTimer
и (если он владел workTimer
) избавляется от workTimer через release:
Но теперь, как вы решаете следующую ситуацию?
Несколько владельцев: установка как в начальном состоянии, но теперь с несколькими независимыми clientObjects
которые содержат ссылки на yourObject
Нет простого ответа, я в курсе! (Не то чтобы последний много говорил, но...)
Так что мой совет...
-
Не делайте свой таймер собственностью/не предоставляйте доступ к нему! Вместо этого, держите его в
ivar
(с современной средой исполнения, я думаю, вы можете зайти так далеко, чтобы определитьivar
в расширении класса) и работать с ним только из одного единственного объекта. (Вы можете сохранить его, если вам удобнее, но в этом нет никакой необходимости.)- Предостережение: если вам абсолютно необходим доступ к таймеру из другого объекта, сделайте так, чтобы свойство
retain
таймер (так как это единственный способ избежать сбоев, вызванных клиентами, которые напрямую аннулировали таймер, к которому они обращались), и предоставьте свой собственный установщик. Перенастройка таймера, на мой взгляд, не очень хорошая причина нарушать инкапсуляцию: предоставьте мутатор, если вам нужно это сделать.
- Предостережение: если вам абсолютно необходим доступ к таймеру из другого объекта, сделайте так, чтобы свойство
-
Установите таймер с целью, отличной от себя. (Существует множество способов сделать это. Может быть, путем написания универсального класса
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, но хорошо...)