2013-01-23 42 views
6

Dưới đây là vấn đề của tôi:Làm thế nào để biết khi nào để vô hiệu hóa một 'NSTimer`

Tôi có một lớp mô hình mà có NSTimer trong nó mà tôi muốn Timer để chạy cho toàn bộ vòng đời của đối tượng mô hình. Initiliazation rất dễ dàng: Tôi chỉ có dòng mã sau đây trong phương pháp init:

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

Tuy nhiên, vấn đề của tôi là, làm thế nào để vô hiệu hóa bộ đếm thời gian này khi mô hình được phát hành từ bộ nhớ? Bây giờ, điều này thường sẽ dễ dàng, tuy nhiên, theo như tôi biết, khi bạn lên lịch một hệ điều hành NSTimer duy trì một con trỏ mạnh đến đối tượng Timer.

Tôi nên giải quyết vấn đề này bằng cách nào? Có phương pháp nào được gọi ngay trước khi mô hình được giải phóng khỏi bộ nhớ không?

+1

Tôi chưa bao giờ thực sự sử dụng này trước khi ... Khi tôi đang học Objective-C Tôi đã luôn nói rằng 'dealloc' hiếm khi được sử dụng nữa. Các thuộc tính của tôi có còn hợp lệ trong phương thức 'dealloc' không? – Nosrettap

+0

Làm thế nào về dealloc? Có, họ sẽ được. Tôi đang gõ nó lên như một câu trả lời. –

+0

Tuyệt! Nếu bạn đăng câu trả lời này, tôi sẽ chấp nhận nó – Nosrettap

Trả lời

21

Các [NSTimer scheduledTimerWithTimeInterval:...]vẫn giữ được mục tiêu, vì vậy nếu mục tiêu là tự, sau đó bạn thể hiện của các lớp mô hình sẽ không bao giờ được deallocated.

Như một giải pháp thay thế, người ta có thể sử dụng một đối tượng riêng biệt (được gọi là TimerTarget trong ví dụ sau). TimerTarget có tham chiếu yếu tới ModelClass để tránh chu kỳ lưu giữ.

"Lớp trợ giúp" này trông như thế này. Mục đích duy nhất của nó là để chuyển tiếp sự kiện hẹn giờ đến "đích thực".

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

@implementation TimerTarget 

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

@end 

Bây giờ, trong lớp mô hình của bạn, bạn có thể tạo một bộ đếm thời gian và vô hiệu hóa nó trong 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 

Vì vậy, chúng tôi có

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

Lưu ý rằng không có (mạnh) tham chiếu từ bộ đếm thời gian đến thể hiện của lớp mô hình nữa.

Tôi đã thử nghiệm này với đoạn mã sau, mà tạo ra một thể hiện của ModelClass và phát hành nó sau 5 giây:

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

Output:

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, Vì không có cách nào đơn giản khác để làm điều này, tôi nghĩ rằng đây sẽ là lựa chọn tốt hơn trong trường hợp này. – iDev

+3

Người ta có thể đóng gói nó trong một lớp 'AutoreleasingTimer' tùy chỉnh. –

+0

Theo tài liệu của Apple, chúng tôi không nên cố gắng phân lớp NSTimer '. Có lẽ một lớp AutoreleasingTimer có thể có các phương thức factory trả về các đối tượng NSTimer với các mục tiêu của chúng được đặt thành một lớp AutoreleasingTimerTarget bên trong? –

0

Dựa trên ý tưởng @ Martin R , Tôi tạo lớp tùy chỉnh dễ sử dụng hơn và thêm một số kiểm tra để tránh sự cố.

@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 

Đây là lớp mẫu tôi

@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 
Các vấn đề liên quan