重复 NSTimer、弱引用、拥有引用还是 iVar?
Posted
技术标签:
【中文标题】重复 NSTimer、弱引用、拥有引用还是 iVar?【英文标题】:Repeating NSTimer, weak reference, owning reference or iVar? 【发布时间】:2011-06-24 02:54:11 【问题描述】:我想我会在这里提出这个问题,作为我之前的一个单独问题 retaining-repeating-nstimer-for-later-access 随着讨论的推进,新问题比另一个编辑更清晰:
场景是一个对象创建一个重复的 NSTimer,让我们在 viewDidLoad 中说,一旦创建了 NSTimer 就需要保留,以便其他方法可以访问它。
NSTimer *ti = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(updateDisplay:)
userInfo:nil
repeats:YES];
我了解,在创建时,runloop 会获得 NSTimer 的所有权,并最终在调用 [ti invalidate];
时停止、删除和释放 NSTimer。
由于我们需要以不止一种方法访问 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 的附注,5 个示例代码项目中有 4 个使用分配**到保留属性的 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,而不是使用属性设置器。
【问题讨论】:
Apple 的最后一个例子看起来非常不正确,但我看到了where you got it from。该属性被声明为retain
,但实际上并未保留计时器——dealloc
中的最终release
应该会导致崩溃。我错过了什么吗?
@Daniel Dickison 这不会崩溃的原因是,该代码示例中的dealloc
(当您在模拟器中构建和调试它时)实际上从未被调用 - 这在一定程度上是有道理的,因为该对象似乎应该与应用程序一样长......也就是说,你是对的:这是完全错误的。
【参考方案1】:
经过深思熟虑并在我的推理中发现一个重要缺陷后,我得出了一个不同的结论:
无关紧要,您是否持有对需要失效的计时器的拥有或非拥有引用。这完全是一个品味问题。
交易破坏者是,计时器的目标是什么:
如果创建计时器的对象是其目标,则管理该对象的生命周期变得更加脆弱:它不能简单地进行保留/释放管理,相反,您需要确保持有对该对象的最后引用的客户端使其无效处理它之前的计时器。
让我用几个对象图来说明这种情况:
您从设置计时器并将自己设置为目标的状态开始。定时器的设置:yourObject
归 someClientObject
所有。并行存在带有一组 scheduleTimers 的当前运行循环。 setupTimer 方法被调用 yourObject
:
结果是下面的初始状态。除了以前的状态yourObject
现在有一个对workTimer
的引用(拥有或不拥有),而workTimer
又拥有yourObject
。此外,workTimer
归 run-loops scheduledTimers 数组所有:
所以现在您将使用该对象,但是当您完成它并简单地释放它时,您将得到简单的释放泄漏:在someClientObject
通过一个简单的处理 yourObject
释放,yourObject
与对象图解除关联,但由workTimer
保持活动状态。 workTimer
和 yourObject
泄露!
您在哪里泄漏对象(和计时器),因为 runloop 使计时器保持活动状态,而计时器又保持对您的对象的拥有引用。
如果yourObject
一次曾经由一个实例拥有,则可以避免这种情况,当它通过取消正确处置时:在处置之前yourObject
通过发布,someClientObject
调用 yourObject 上的 cancelTimer
方法。在该方法中,yourObject 使 workTimer
无效,并且(如果它拥有 workTimer
)通过发布释放 workTimer:
但是现在,您如何解决以下情况?
多个所有者:设置与初始状态类似,但现在有多个独立的 clientObjects
持有对 yourObject
的引用
没有简单的答案,我知道! (不是后者要多说,而是……)
所以我的建议是……
不要让你的计时器成为一个属性/不要为它提供访问器!相反,保持它私有(我认为使用现代运行时,您可以在类扩展中定义ivar
)并且只从一个对象处理它。 (如果您觉得这样做更舒服,您可以保留它,但绝对没有必要。)
retain
设置为计时器(因为这是避免由客户端导致的崩溃的唯一方法)直接使他们访问的计时器无效)并提供您自己的设置器。在我看来,重新安排计时器并不是打破封装的好理由:如果需要,请提供一个 mutator。
使用除自己以外的目标设置计时器。 (有很多方法可以做到这一点。也许通过编写一个通用的TimerTarget
类,或者——如果你可以使用它——通过一个MAZeroingWeakReference
?)
对于第一次讨论中的白痴,我深表歉意,并感谢 Daniel Dickison
和 Rob Napier 的耐心等待。
这就是我从现在开始处理计时器的方式:
// 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
原创(用于完整披露):
你从your other post知道我的意见:
几乎没有理由拥有计划计时器(和bbum seems to agree)的引用。
也就是说,您的选项 2 和 3 本质上是相同的。 ([self setWalkTimer:nil]
与 walkTimer = nil
相比还涉及额外的消息传递,但我不确定编译器是否不会优化它并直接访问 ivar,但好吧......)
【讨论】:
是的,抱歉,2 中的版本不应该存在(编辑删除) 谢谢,所以weak reference(2) 或iVar(3) 是最好的选择。 好吧,就像我说的:选项 2 和 3 基本相同。如果你打算使用选项 2,那么编写你自己的 setter 以确保旧的计时器将失效 - 这样,你可以完全摆脱cancelTimer
或将其干燥为 self.walkTimer=nil
。【参考方案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];
【讨论】:
非常感谢 Rob,所以我可以从这个和你以前的 cmets 中看出你要保留计时器吗? 是的。我总是保留 ivars,除非有充分的理由不(特别是代表)。这可以最大限度地减少调用者的猜测。以上是关于重复 NSTimer、弱引用、拥有引用还是 iVar?的主要内容,如果未能解决你的问题,请参考以下文章