有没有发现在使用NSTimer类的定时器,实现定时器的类并没有走dealloc方法?这是为什么?
NSTimer常用定时器写法
正常我们利用NSTimer方式实现代码如下:
@property (nonatomic, strong) NSTimer *tanMukuTimer;
- (void)stopTanMuku
{
[_tanMukuTimer invalidate];
_tanMukuTimer = nil;
}
- (void)beginTanMuKu
{
if (_tanMukuTimer == nil) {
_tanMukuTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(requestNetwork) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_tanMukuTimer forMode:NSDefaultRunLoopMode];
}
}
问题
用此方法创建的定时器会在指定的间隔时间执行定时器的方法-requestNetwork,知道开发者稍后将其手动关闭为止,target与selector参数标识计时器将在哪个对象上调用哪个方法,计时器会保留目标对象,等到自身失效,才会释放目标对象,因为计时器是用实例变量来存放的,所以实例也保留了计时器,然而就出现了保留环。除非在某个时间点手动调用-stopTanMuku 方法停止计时器,从而打破保留环,但通过调用方法来避免内存哦泄漏,手动干预的方式并不理想。如果想在系统回收本类时调用停止计时器的方案,打破循环引用,就会出现问题,因为计时器对象有引用计数,所以self实例的保留计数不会为0,因此系统就不会调用dealloc方法,而现在有没有人调用停止计时器的方法,所以就会出现内存泄漏,类永远无法被释放掉。如果是计时器调用的是请求数据方法,那就导致更多其他严重的内存泄漏问题。
解决方法
这种方法无法通过代码检测出来,因此我们要对这个问题进行避免,可以通过block进行解决,因此要写个NSTimer的分类:
@interface NSTimer (WPGBlockSupport)
+ (NSTimer *)wpg_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)(void))block
repeats:(BOOL)repeats;
@end
#import "NSTimer+WPGBlockSupport.h"
@implementation NSTimer (WPGBlockSupport)
+ (NSTimer *)wpg_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)(void))block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(wpg_blockInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)wpg_blockInvoke:(NSTimer *)timer
{
void (^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
将block作为userinfo的参数传进去,只要计时器还有效,就会一直保留着它,传入参数时要通过copy方法将block,copy到堆上,否则等到稍后要执行它的时候,该块可能已经无效了。计时器现在的target是NSTimer的类对象,由于是个单例,所以不用担心产生循环引用的问题。但是这本身还不能解决问题,因为block捕获了self变量,所以block要保留实例,计时器有通过userinfo保留了block,最后实例本身还要保留计时器,不过改成weak引用,就直接打破保留环,打破循环引用。
- (void)upstartQueryTimer
{
[self upstopQueryTimer];
if (nil == _upqueryNoticeTimer) {
__weak typeof(self) weakSelf = self;
_upqueryNoticeTimer = [NSTimer wpg_scheduledTimerWithTimeInterval:3.0 block:^{
__strong typeof(self) strongself = weakSelf;
[strongself startAnimition];
} repeats:NO];
}
}
这段代码先定义了弱引用,然后通过weak变量不直接捕获self变量,这样self就不会被计时器所保留,当块开始执行时,而不直接捕获普通的self变量,当开始执行时,立刻在block内生成strong引用,以保证实例执行期间可以存活,但由于是临时变量,strong引用的self并没有存在堆中,执行后自动就被释放了。这样就完美解决了,不会调用系统不会调用dealloc的内存泄漏问题。
思考与行动:
- 除了通过NSTimer实现计时器外,还有其他实现定时器的方式么?哪个更好用?
- 通过GCD实现的定时器会出现内存泄漏的问题吗?GCD是如何实现定时器方法的?