前面个项目有用NSTimer做个短信验证码的定时器, 遇到了这个问题.
关于NSTimer, 有一点需要特别注意:NSTimer会持有target(Remember that NSTimer Retains Its Target)。
-
一个NSTimer对象在触发时会保留目标直到计时器被显式的设置无效。(NSTimer invalidate)
-
如果调用者保存了NSTimer, 所以用完之后应该立即销毁定时器(或者退出当前视图的销毁等) 否则, 会有内存泄漏。
-
使用块方式可以使NSTimer对象实现打破这种循环引用的扩展。为了使这个块实现作为NSTimer的公共接口一部分,可以把这个块实现方法加到NSTimer的类扩展中。
(代码) -
// Example showing retain cycle #import <Foundation/Foundation.h> @interface EOCClass : NSObject - (void)startPolling; - (void)stopPolling; @end @implementation EOCClass { NSTimer *_pollTimer; } - (id)init { return [super init]; } - (void)dealloc { [_pollTimer invalidate]; } - (void)stopPolling { [_pollTimer invalidate]; _pollTimer = nil; } - (void)startPolling { _pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(p_doPoll) userInfo:nil repeats:YES]; } - (void)p_doPoll { // Poll the resource } @end // Block support for NSTimer #import <Foundation/Foundation.h> @interface NSTimer (EOCBlocksSupport) + (void)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats; @end @implementation NSTimer (EOCBlocksSupport) + (void)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats { return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(eoc_blockInvoke:) userInfo:[block copy] repeats:repeats]; } + (void)eoc_blockInvoke:(NSTimer*)timer { void (^block)() = timer.userInfo; if (block) { block(); } } @end // Changing to use the block - still a cycle - (void)startPolling { _pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{ [self p_doPoll]; } repeats:YES]; } // No cycle using weak references - (void)startPolling { __weak EOCClass *weakSelf = self; _pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{ EOCClass *strongSelf = weakSelf; [strongSelf p_doPoll]; } repeats:YES];
所以,如果你不想实现block的方式,在dealloc中记得invalidate timer就可以了。 也可以看看这篇文章Defeating NSTimer Retain Cycle
Defeating NSTimer Retain Cycle
If you’ve done any coding with NSTimer and ARC you’ll know the pain of having to manually invalidate your timer before its owner can be deallocated.
Typically, you’ll have to have your object’s parent reach into the object’s inner workings and invalidate the timer at the appropriate time. This is inconvenient and breaks encapsulation.
I have a UITableViewCell which utilizes a NSTimer to keep track of energy regeneration, and I was determined to make it work without any outside intervention.
My solution was to utilize the UITableViewCell’s willMoveToWindow: method like so:
- (void)willMoveToWindow:(UIWindow *)newWindow
{
[super willMoveToWindow:newWindow];
if (newWindow == (id)[NSNull null] || newWindow == nil) {
[self stopTimer];
}
else {
[self refreshEnergy];
}
}
- (void)stopTimer
{
[timer invalidate];
timer = nil;
}
- (void)refreshEnergy
{
if (timer == nil) {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(refreshEnergy) userInfo:nil repeats:YES];
}
...
}
Now the UITableVIewCell automatically starts and stops the repeating timer as needed and deallocates correctly.
A note: You might try to simple use a weak reference to self in the NSTimer constructor but you’ll find that the timer must be invalidated to deallocate correctly.This WON’T work:
__weak MyClass *weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:weakSelf selector:@selector(refreshEnergy) userInfo:nil repeats:YES];