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

时间:2011-02-09 12:47:59

标签: iphone objective-c cocoa-touch nstimer

我想我会把它作为我之前的一个单独的问题 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而不使用属性设置器。

2 个答案:

答案 0 :(得分:18)

修改

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

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

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

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

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

  1. 您从设置计时器并将自己设置为目标的状态开始:
    Setup of the Timer: yourObject is owned by someClientObject. In parallel exists the current run-loop with an array of scheduledTimers. the setupTimer method is called upon yourObject http://a.yfrog.com/img616/8947/fqlc.png
  2. 结果是以下初始状态:
    Initial state: In addition to the former state yourObject now has a reference (owned or not) to the workTimer, which in turn owns yourObject. Furthermore, workTimer is owned by the run-loops scheduledTimers array. http://a.yfrog.com/img640/3444/acq.png
  3. 所以现在你将使用这个对象,但是当你完成它并简单地释放它时,你最终会得到 Simple release leak: after someClientObject disposes of yourObject through a simple release, yourObject is disassociated from the object-graph but kept alive by workTimer. workTimer and yourObject are leaked! http://a.yfrog.com/img610/7223/jyyj.png
    泄漏对象(和计时器)的地方,因为runloop使计时器保持活动状态,而这反过来又保留了对象的拥有引用。
  4. 如果你的对象只有曾经拥有一个单一实例,当它被妥善处理时,可以避免这种情况:
    Proper disposal through cancellation: before disposing of yourObject through release, someClientObject calls the cancelTimer method on yourObject. Within that method, yourObject invalidates workTimer and (if it owned workTimer) disposes of workTimer through release http://a.yfrog.com/img614/7428/p8af.png

    但是现在,你如何解决以下情况? Multiple Owners: Setup like in the initial state, but now with multiple independent clientObjects that hold references to yourObject http://a.yfrog.com/img619/3908/wqe.png

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

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

    1. 不要让你的计时器成为财产/不为它提供访问者!相反,保持它是私有的(现代运行时我认为你甚至可以在类扩展中定义ivar)并且只从一个对象处理它。 (你可以保留它,如果你觉得这样做更舒服,但绝对没有必要。)
      • 警告: 如果您绝对需要从另一个对象访问计时器,请将属性retain设为计时器(因为这是避免的唯一方法)客户端导致的崩溃直接使他们访问的计时器失效。提供您自己的setter。在我看来,重新安排一个计时器并不是打破封装的好理由:如果你需要这样做,就提供一个改变器。
    2. 使用self以外的目标设置计时器。 (有很多方法可以这样做。也许通过写一个通用的TimerTarget类或 - 如果你可以使用它 - 通过MAZeroingWeakReference?)
    3. 我为第一次讨论中的白痴而道歉,并要感谢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,但是... ...

答案 1 :(得分: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];