2013-01-23 10 views
6

Hier ist mein Problem zu entkräften:Wie kann man wissen, wenn ein `NSTimer`

ich eine Modellklasse haben, die eine NSTimer darin hat, dass ich der Timer für die gesamte Lebensdauer des Modellobjekts auszuführen. Initiliazation ist einfach: Ich habe gerade die folgende Codezeile in der init Methode:

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

aber mein Problem ist, wie ungültig ich diese Zeit, wenn das Modell aus dem Speicher freigegeben wird? Nun, das wäre normalerweise einfach, aber soweit ich weiß, wenn Sie ein NSTimer planen, behält das Betriebssystem einen starken Zeiger auf das Timer-Objekt bei.

Wie soll ich damit umgehen? Gibt es eine Methode, die aufgerufen wird, bevor das Modell aus dem Speicher freigegeben wird?

+1

Ich habe das wirklich noch nie benutzt ... Als ich Objective-C wurde das Lernen gesagt, ich war immer, dass 'dealloc' selten mehr verwendet wird. Werden meine Eigenschaften in der Dealloc-Methode noch gültig sein? – Nosrettap

+0

Wie wäre es mit dealloc? Ja, werden sie sein. Ich tippe es als Antwort ein. –

+0

Cool! Wenn Sie dies als Antwort posten, akzeptiere ich es – Nosrettap

Antwort

21

Die [NSTimer scheduledTimerWithTimeInterval:...]behält sich das Ziel, so dass, wenn das Ziel Selbst, dann Instanz der Modellklasse wird nie freigegeben werden.

Als Workaround kann ein separates Objekt verwendet werden (im folgenden Beispiel TimerTarget). TimerTarget hat eine schwache Referenz auf ModelClass, um einen Retain-Zyklus zu vermeiden.

Diese "Helferklasse" sieht so aus. Der einzige Zweck besteht darin, das Timer-Ereignis an das "echte Ziel" weiterzuleiten.

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

@implementation TimerTarget 

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

@end 

nun in Ihrem Modell-Klasse können Sie einen Timer erstellen und ungültig machen es in 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 

So haben wir

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

Hinweis, dass es keine (starken) Referenz vom Timer auf die Instanz der Modellklasse.

Ich habe dies mit dem folgenden Code getestet, die eine Instanz von ModelClass und gibt sie nach 5 Sekunden erstellt:

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

Ausgang: Idee

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, Da es keine anderen einfachen Möglichkeiten gibt, dies zu tun, denke ich, dass dies in diesem Fall die bessere Option wäre. – iDev

+3

Man könnte dies wahrscheinlich in einer benutzerdefinierten 'AutoreleasingTimer'-Klasse einkapseln. –

+0

Gemäß den Apple-Dokumenten sollten wir nicht versuchen, NSTimer zu unterklassen. Vielleicht könnte eine solche AutoReleasingTimer-Klasse über Factory-Methoden verfügen, die NSTimer-Objekte zurückgeben, deren Ziele auf eine interne AutoReleasingTimerTarget-Klasse festgelegt sind? –

0

Basierend auf @ Martin R Ich erstelle benutzerdefinierte Klassen, die einfacher zu verwenden sind, und füge einige Überprüfungen hinzu, um einen Absturz zu vermeiden.

@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 

Hier ist meine Beispielklasse

@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