重复 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】:

经过深思熟虑并在我的推理中发现一个重要缺陷后,我得出了一个不同的结论:

无关紧要,您是否持有对需要失效的计时器的拥有或非拥有引用。这完全是一个品味问题。

交易破坏者是,计时器的目标是什么:

如果创建计时器的对象是其目标,则管理该对象的生命周期变得更加脆弱:它不能简单地进行保留/释放管理,相反,您需要确保持有对该对象的最后引用的客户端使其无效处理它之前的计时器。

让我用几个对象图来说明这种情况:

    您从设置计时器并将自己设置为目标的状态开始。定时器的设置:yourObjectsomeClientObject 所有。并行存在带有一组 scheduleTimers 的当前运行循环。 setupTimer 方法被调用 yourObject:

    结果是下面的初始状态。除了以前的状态yourObject 现在有一个对workTimer 的引用(拥有或不拥有),而workTimer 又拥有yourObject。此外,workTimer 归 run-loops scheduledTimers 数组所有:

    所以现在您将使用该对象,但是当您完成它并简单地释放它时,您将得到简单的释放泄漏:在someClientObject 通过一个简单的处理 yourObject释放,yourObject 与对象图解除关联,但由workTimer 保持活动状态。 workTimeryourObject 泄露!

您在哪里泄漏对象(和计时器),因为 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)的引用。

也就是说,您的选项 23 本质上是相同的。 ([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?的主要内容,如果未能解决你的问题,请参考以下文章

NSTimer和实现弱引用的timer的方式

强引用strong和弱引用weak的定义

java中弱引用是怎么回事啊?

强引用弱引用软引用虚引用

ALAssetsLibrary resultBlock 弱引用或强引用

学习PHP弱引用的知识