重复NSTimer,弱参考,拥有参考或iVar?

fuz*_*oat 10 iphone cocoa-touch objective-c nstimer

我想我会把这个作为一个单独的问题从我之前的 保留 - 重复 - 时间 - 后续访问中解决,因为讨论已经向前发展,使得一个新问题比另一个编辑更清晰:

场景是一个对象创建一个重复的NSTimer,让我们说在viewDidLoad中,一旦创建了NSTimer需要留在那里,所以它可以通过其他方法访问.

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

我知道在创建时,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];
Run Code Online (Sandbox Code Playgroud)

.

// (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];
Run Code Online (Sandbox Code Playgroud)

.

// (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;
Run Code Online (Sandbox Code Playgroud)

.

// (4) Something not listed above ...
Run Code Online (Sandbox Code Playgroud)

我很高兴只有(1)(2)(3)或(4),因为关于哪个最好的讨论已经写在其他线程上.似乎确实有很多相互矛盾的答案,所以我希望这个更具体的问题将有助于关注这种情况下最佳实践.


编辑:

作为Apple NSTimer类参考文献的附注,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];
Run Code Online (Sandbox Code Playgroud)

**应该注意的是,在示例中,Apple直接分配iVar而不使用属性设置器.

dan*_*dee 18

编辑

在给出了更多的思考并在我的推理中找到一个重要的缺陷之后,我得出了一个不同的结论:

无论您是否拥有对需要使其无效的计时器的拥有或非拥有引用都无关紧要.这完全是品味问题.

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

如果创建计时器的对象是其目标,那么管理该对象的生命周期就会变得更加脆弱:它不能简单地保留/释放托管,而是需要确保持有对该对象的最后一个引用的客户端使它在之前使计时器无效它处理它.

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

  1. 您从一个设置计时器的状态开始并将自己设置为目标:
    Timer的设置:yourObject由someClientObject拥有.并行存在具有scheduledTimers数组的当前运行循环.在您的对象上调用setupTimer方法http://a.yfrog.com/img616/8947/fqlc.png
  2. 结果是以下初始状态:
    初始状态:除了前一个状态之外,yourObject现在还有一个对workTimer的引用(拥有或不拥有),而workTimer又拥有yourObject.此外,workTimer由run循环scheduledTimers数组拥有.http://a.yfrog.com/img640/3444/acq.png
  3. 所以现在你将使用该对象,但是当你完成它并简单地释放它时,你最终会发现
    Simple release leak:在someClientObject通过一个简单的版本处理你的对象之后,你的对象与对象图解除了关联但是通过workTimer保持活力.workTimer和yourObject被泄露了!http://a.yfrog.com/img610/7223/jyyj.png
    泄漏对象(和计时器),因为runloop使计时器保持活动状态,而这反过来又保留了对象的拥有引用.

如果yourObject 一次只由一个实例拥有,当它被正确处理时,可以避免这种情况:
通过取消正确处理:在通过发布处理yourObject之前,someClientObject在yourObject上调用cancelTimer方法.在该方法中,yourObject使workTimer失效,并且(如果它拥有workTimer)通过发布http://a.yfrog.com/img614/7428/p8af.png释放workTimer

但是现在,您如何解决以下情况?
多个所有者:在初始状态下进行设置,但现在有多个独立的clientObject,它们包含对象的引用http://a.yfrog.com/img619/3908/wqe.png

我知道,没有简单的答案!(不是后者必须多说,但......)

所以我的建议是......

  1. 不要让你的计时器成为财产/不为它提供访问者!相反,保持私有(使用现代运行时,我认为你可以在类扩展中定义ivar)并且只从一个对象处理它.(你可以保留它,如果你觉得这样做更舒服,但绝对没有必要.)
    • 警告: 如果您确实需要从另一个对象访问定时器,使财产yourObject定时器(因为这是避免因直接失效,他们访问的定时器客户端崩溃的唯一办法),并提供自己的二传手.在我看来,重新安排一个计时器并不是打破封装的好理由:如果你需要这样做,就提供一个mutator.
  2. 使用self以外的目标设置计时器.(有很多方法可以这样做.也许通过编写泛型someClientObject类或 - 如果可以使用它 - 通过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
Run Code Online (Sandbox Code Playgroud)

原件(完整披露):

您从其他帖子中了解我的意见:

没有理由拥有预定计时器的参考(并且bbum似乎同意).

也就是说,您的选项23基本相同.(有涉及额外的消息yourObjectyourObject,但我不知道如果编译器不会优化该走并直接访问实例变量,但是呢......)