2013-01-23 13 views
6

Oto mój problem:Jak wiadomo, kiedy do unieważnienia obiekt `NSTimer`

Mam klasy modelu, który ma NSTimer w nim, że licznik ma być prowadzony przez cały czas eksploatacji obiektu modelu. Initiliazation jest łatwe: po prostu mam następujący wiersz kodu w metodzie init:

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

Jednak moim problemem jest to, jak mogę unieważnić ten programator, gdy model jest zwolniony z pamięci? Zwykle będzie to jednak łatwe, o ile wiem, kiedy zaplanujesz NSTimer, system operacyjny utrzymuje silny wskaźnik do obiektu Timer.

Jak mam sobie z tym poradzić? Czy istnieje metoda, która jest wywoływana tuż przed zwolnieniem modelu z pamięci?

+1

Nigdy naprawdę użył tego wcześniej ... Kiedy uczyłem Objective-C Zawsze byłem powiedział, że 'dealloc' rzadko już używane. Czy moje właściwości będą nadal ważne w metodzie "dealloc"? – Nosrettap

+0

Co powiesz na dealloc? Tak, będą. Piszę to jako odpowiedź. –

+0

Fajnie! Jeśli opublikujesz to jako odpowiedź, zaakceptuję to. – Nosrettap

Odpowiedz

21

The [NSTimer scheduledTimerWithTimeInterval:...]zachowuje cel, więc jeśli cel jest własnym, wówczas instancja klasy modelu nigdy nie będą zwalniane.

Jako obejście można użyć oddzielnego obiektu (w poniższym przykładzie nazwanego TimerTarget). TimerTarget ma słabe oznaczenie dla jako odniesienie do ModelClass, aby uniknąć cyklu zatrzymania.

Ta "klasa pomocnika" wygląda tak. Jego jedynym celem jest przekazanie zdarzenia licznika czasu do "rzeczywistego celu".

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

@implementation TimerTarget 

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

@end 

Teraz, w swojej klasie modelu, można utworzyć timera i unieważnia go w 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 

Więc mamy

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

pamiętać, że nie ma (silny) odniesienie z timera do instancji klasy modelu już.

ja testowałem to z następującego kodu, który tworzy instancję ModelClass i uwalnia go po 5 sekundach:

__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; 
}); 

wyjściowa:

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, ponieważ nie ma innych prostych sposobów, aby to zrobić, myślę, że byłaby to lepsza opcja w tym przypadku. – iDev

+3

Prawdopodobnie można to zaimplementować w niestandardowej klasie "AutoreleasingTimer". –

+0

Zgodnie z dokumentacją Apple, "nie powinniśmy próbować podklasy NSTimer". Może taka klasa AutoreleasingTimer może mieć fabryczne metody, które zwracają obiekty NSTimer z ich celami ustawionymi na wewnętrzną klasę AutoreleasingTimerTarget? –

0

podstawie @Martin R pomysł , Tworzę niestandardową klasę, która jest łatwiejsza w użyciu i dodaje kilka sprawdzeń, aby uniknąć awarii.

@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 

Oto moja klasa próbka

@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