NSTimer保留对象导致内存泄漏
2016-06-04 本文已影响464人
funnyS
在做限时支付,验证码发送之类的功能时经常需要使用NTimer来做定时器,但是NSTimer在invalidate之前会保留持有它target对象,导致targtet对象无法释放,即使在delloc中:
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
也无法释放,因为target对象一直被引用就不会进入delloc方法。
- 所以最基本的方法就是在target对象需要释放之前手动去控制执行计时器的invalidate方法,但有时计时器的invalidate方法并不是完全能控制调用的,因为要确定target对象在最后一个引用释放之前调用计时器的invalidate方法,这通过代码无法完全检测出来。在查看了《Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法》之后得知可以用块来打破“保留环”。
首先,在分类中添加下面这段代码:
#import <Foundation/Foundation.h>
@interface NSTimer (Addtions)
/**
* 计时器动作
*
* @param interval 时间
* @param block 事件
* @param repeats 是否重复
*/
+ (NSTimer *)sf_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
#import "NSTimer+Addtions.h"
@implementation NSTimer(Addtions)
/**
* 计时器动作
*
* @param interval 时间
* @param block 事件
* @param repeats 是否重复
*/
+ (NSTimer *)sf_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(private_blockinvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)private_blockinvoke:(NSTimer *)timer{
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
-
在代码中,把要执行的事件封装成“块”,通过userInfo参数传出去(uerInfo可用来存放“不透明值”),那么只要计时器有效,就会一直保留着它。这里有个地方要注意一下,传递block时需要copy一下,把block拷贝到“堆”上,因为定义块的时候,所占的内存区域是分配在栈中的,这说明,块只在定义它的那个范围内有效,通过拷贝就可以把块从栈复制到堆上,块就可以在定义它的范围之外使用。
-
现在计时器的target是NSTimer类对象,而NSTimer类对象是个单例,是否持有它都无所谓了。
现在我们直接使用这个函数去创建计时器:
self.timer = [NSTimer sf_scheduledTimerWithTimeInterval:1.0
block:^{
[self timerAction];
} repeats:YES];
这时依然会形成“保留环”,因为block保留了self对象,self又持有timer属性。所以在调用函数之前要使用weak引用来打破它:
__weak RegistVC *weakSelf = self;
self.timer = [NSTimer sf_scheduledTimerWithTimeInterval:1.0
block:^{
RegistVC *strongSelf = weakSelf;
[strongSelf timerAction];
} repeats:YES];
到这里,问题就算解决了,当self的最后一个引用将其释放的时候,self就会被释放了,如果忘记在delloc中调用计时器的invalidate方法,则weakSelf会变为nil。在调试过程中也能看到对象delloc了,而不是再持有一段时间甚至永远不会释放。