2013-01-23 70 views
6

這裏是我的問題:如何知道什麼時候失效的`NSTimer`

我有一個其中有一個NSTimer,我希望定時器爲模型對象的整個生命週期運行模型類。 Initiliazation很簡單:我只是代碼在init方法如下一行:

self.maintainConnectionTimer = 
      [NSTimer scheduledTimerWithTimeInterval:1 
               target:self 
              selector:@selector(maintainConnection) 
              userInfo:nil 
              repeats:YES]; 

不過,我的問題是,我怎麼否定這一計時器,當模型從內存中釋放?現在,這通常很容易,但是,據我所知,當您計劃NSTimer時,操作系統維護一個強指針指向Timer對象。

我該如何處理?是否有一種方法在模型從內存中釋放之前被調用?

+1

我從來沒有真正使用過這...當我在學習Objective-C,我總是告訴'dealloc'很少使用了。我的屬性在'dealloc'方法中仍然有效嗎? – Nosrettap

+0

dealloc怎麼樣?是的,他們會。我將它打成答案。 –

+0

酷!如果您將此作爲答案發布,我會接受它 – Nosrettap

回答

21

[NSTimer scheduledTimerWithTimeInterval:...]保留目標,因此,如果目標是自我,那麼你的模型類的實例將永遠不會被釋放。

作爲解決方法,可以使用單獨的對象(在以下示例中稱爲TimerTarget)。 TimerTarget有一個參考ModelClass,以避免保留週期。

這個「助手類」看起來像這樣。它唯一的目的是將定時器事件轉發給「真實目標」。現在

@interface TimerTarget : NSObject 
@property(weak, nonatomic) id realTarget; 
@end 

@implementation TimerTarget 

- (void)timerFired:(NSTimer*)theTimer 
{ 
    [self.realTarget performSelector:@selector(timerFired:) withObject:theTimer]; 
} 

@end 

,在你的模型類,你可以在dealloc創建一個定時器,並使它無效:

@interface ModelClass() 
@property(strong, nonatomic) NSTimer *timer; 
@end 

@implementation ModelClass 

- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     TimerTarget *timerTarget = [[TimerTarget alloc] init]; 
     timerTarget.realTarget = self; 
     self.timer = [NSTimer scheduledTimerWithTimeInterval:1 
               target:timerTarget 
               selector:@selector(timerFired:) 
               userInfo:nil repeats:YES]; 
    } 
    return self; 
} 

- (void)dealloc 
{ 
    [self.timer invalidate]; // This releases the TimerTarget as well! 
    NSLog(@"ModelClass dealloc"); 
} 

- (void)timerFired:(NSTimer*)theTimer 
{ 
    NSLog(@"Timer fired"); 
} 

@end 

因此,我們必須

modelInstance ===> timer ===> timerTarget ---> modelInstance 
(===> : strong reference, ---> : weak reference) 

注意,沒有(強)從定時器引用模型類的實例。

我已用下面的代碼,其5秒後的創建和ModelClass釋放它的一個實例測試此:

__block ModelClass *modelInstance = [[ModelClass alloc] init]; 
int64_t delayInSeconds = 5.0; 
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
    modelInstance = nil; 
}); 

輸出:

2013-01-23 23:54:11.483 timertest[16576:c07] Timer fired 
2013-01-23 23:54:12.483 timertest[16576:c07] Timer fired 
2013-01-23 23:54:13.483 timertest[16576:c07] Timer fired 
2013-01-23 23:54:14.483 timertest[16576:c07] Timer fired 
2013-01-23 23:54:15.483 timertest[16576:c07] Timer fired 
2013-01-23 23:54:15.484 timertest[16576:c07] ModelClass dealloc 
+2

+1,既然沒有其他簡單的方法可以做到這一點,我認爲在這種情況下這將是更好的選擇。 – iDev

+3

可以將其封裝在自定義的'AutoreleasingTimer'類中。 –

+0

根據蘋果公司的文件,我們不應該嘗試子類化NSTimer'。也許這樣一個AutoreleasingTimer類可以有工廠方法返回NSTimer對象,其目標設置爲一個內部的AutoreleasingTimerTarget類? –

0

基於@馬丁 - [R想法,我創建了更易於使用的自定義類,並添加了一些檢查以避免崩潰。

@interface EATimerTarget : NSObject 

// Initialize with block to avoid missing call back 
- (instancetype)initWithRealTarget:(id)realTarget timerBlock:(void(^)(NSTimer *))block; 

// For NSTimer @selector() parameter 
- (void)timerFired:(NSTimer *)timer; 

@end 

@interface EATimerTarget() 
@property (weak, nonatomic) id realTarget; 
@property (nonatomic, copy) void (^timerBlock)(NSTimer *); // use 'copy' to avoid retain counting 
@end 

@implementation EATimerTarget 

- (instancetype)initWithRealTarget:(id)realTarget timerBlock:(void (^)(NSTimer *))block { 
    self = [super init]; 
    if (self) { 
     self.realTarget = realTarget; 
     self.timerBlock = block; 
    } 
    return self; 
} 

- (void)timerFired:(NSTimer *)timer { 
    // Avoid memory leak, timer still run while our real target is dealloc 
    if (self.realTarget) { 
     self.timerBlock(timer); 
    } 
    else { 
     [timer invalidate]; 
    } 
} 

@end 

這裏是我的示例類

@interface MyClass 
@property (nonatomic, strong) NSTimer *timer; 
@end 

@implementation MyClass 

- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     // Using __weak for avoiding retain cycles 
     __weak typeof(self) wSelf = self; 
    EATimerTarget *timerTarget = [[EATimerTarget alloc] initWithRealTarget:self timerBlock: ^(NSTimer *timer) { 
     [wSelf onTimerTick:timer]; 
    }]; 
     self.timer = [NSTimer timerWithTimeInterval:1 target:timerTarget selector:@selector(timerFired:) userInfo:nil repeats:YES]; 
     [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode]; 
    } 
    return self; 
} 

- (void)dealloc 
{ 
    [self.timer invalidate]; // This releases the EATimerTarget as well! 
    NSLog(@"### MyClass dealloc"); 
} 

- (void)onTimerTick:(NSTimer *)timer { 
    // DO YOUR STUFF! 
    NSLog(@"### TIMER TICK"); 
} 

@end 
相關問題