NSTimer在日常的工作中,经常会被人用到。它经常被用到几个方面
- 定时重复任务
- 延迟任务
- Banner运营控件滑动
- 重复刷新界面
以上这些方面是大部分应用App都会面对的需求,但是NSTimer有一个非常危险的地方,它会间接导致很多的crash,而且这些crash都不好确定,是哪里的代码导致。
NSTimer的危险原因
一般开发者都会如下使用NSTimer,一般都使用schedule开头的方法,因为此类方法封装了runloop相关的操作,使得使用更加方便。
@property (nonatomic, strong) NSTimer* timer;
...
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerDidFire:) userInfo:nil repeats:YES];
...
- (void)timerDidFire:(NSTimer*)timer
{
NSLog(@"%@", @"timerDidFire:");
}
以上的代码,是非常正常的NSTimer实现方式。但是问题就是target是self,我们需要关注系统的NSTimer函数,是否会strong住self???
通过实验,发现系统的确strong住了self,这样导致了循环引用和内存泄露
A class中,定义TBTimerTestObject,该类中有一个NSTimer在不断运行
- (BOOL)TestTimterInAClass
_obj = [[TBTimerTestObject alloc] init];
[self performSelector:@selector(timerDidFired:) withObject:nil afterDelay:1];
return YES;
}
- (void)timerDidFired:(NSTimer*)timer
{
_obj = nil;
NSLog(@"%@", @" AppDelegate timerDidFired");
}
TBTimerTestObject 文件
@interface TBTimerTestObject ()
@property (nonatomic, weak) NSTimer* timer;
@end
@implementation TBTimerTestObject
- (void)dealloc
{
NSLog(@"sssss");
}
- (id)init
{
self = [super init];
if (self) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerDidFire:) userInfo:nil repeats:YES];
}
return self;
}
- (void)timerDidFire:(NSTimer*)timer
{
NSLog(@"%@", @"1111111");
}
@end
执行代码的结果
11111111
AppDelegate timerDidFired
11111111
11111111
11111111
11111111
从结果看,存在内存泄露
NSTimer隐患带来的危害
一般来说,即使出现内存泄露,也不会出现大的问题,但是由于之前的timer对象,内部没有处理好,导致timerdidfired不断运行,那么会出现严重隐患,甚至出现crash。
- 对于共享内存处理
- 对于NSArray/NSDictory的处理
- 对于web的处理
- 等等
所以,要解决内存泄漏的问题,非常关键。
解决方案
对于NSTimer内存泄漏问题,关键是target会hook住self,这样导致self和timer循环引用。那么想解决的方法只有解决循环引用问题。
#import <Foundation/Foundation.h>
@interface TBWeakTimerTarget : NSObject
- (instancetype) initWithTarget: (id)target andSelector:(SEL) selector;
- (void)timerDidFire:(NSTimer *)timer;
@end
#import "TBWeakTimerTarget.h"
@implementation TBWeakTimerTarget
{
__weak id _target;
SEL _selector;
}
- (instancetype) initWithTarget: (id) target andSelector: (SEL) selector
{
self = [super init];
if (self) {
_target = target;
_selector = selector;
}
return self;
}
- (void) dealloc
{
NSLog(@"TBWeakTimerTarget dealloc");
}
- (void)timerDidFire:(NSTimer *)timer
{
if(_target)
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_target performSelector:_selector withObject:timer];
#pragma clang diagnostic pop
}
else
{
[timer invalidate];
}
}
@end
使用方法:
TBWeakTimerTarget* timerTarget = [[TBWeakTimerTarget alloc] initWithTarget:self andSelector:@selector(timerDidFire:)];
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:timerTarget selector:@selector(timerDidFire:) userInfo:nil repeats:YES];
恩,就是这么简单。有了TBWeakTimerTarget保护NSTimer的使用,就会保证NSTimer和Self不会循环引用。
总之,移动产品稳定性是产品性能的重要指标
【叁省 http://blog.youkuaiyun.com/bjtufang 转载烦请注明出处,尊重劳动成果】