NSTimer
是iOS中常用的定时器,通常用来在固定时间间隔重复某个任务。使用起来也比较简单,但是一直以来存在一个问题,就是会造成循环引用,下面先来看下导致循环引用的用法。
假设现在有一个控制器。
@interface MyViewController ()
@property (nonatomic, strong) NSTimer * timer;
@end
在控制器的viewDidLoad
中,我们初始化一个按钮。
- (void)viewDidLoad {
[super viewDidLoad];
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setTitle:@"begin" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
btn.frame = CGRectMake(20, 100, 120, 80);
[btn addTarget:self action:@selector(update) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
点击按钮,初始化timer
。
- (void)update {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(logNumber) userInfo:nil repeats:YES];
}
每隔2秒执行以下方法。
- (void)logNumber {
NSLog(@"22222");
}
看起来很简单,代码也可以顺利运行,但是问题来了,timer
作为控制的属性,初始化时,控制器强引用timer
,在timer
的初始化方法中,self
(即控制器)又作为target
被timer
强引用,毫无疑问,一个循环引用出现了,这将导致两者都不能被释放,也就是说,控制器在被推出的时候,并不会执行dealloc
方法,因此,下面的方法也就不会执行。timer
自然也不会被释放。
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
那要怎么解决呢?这里介绍一种方法,引入中间者。
MyTimerMiddle
是一个中间类,它主要负责timer
的初始化,定时任务的触发以及timer
的销毁,同时,也负责连接起最终要执行任务的目标对象。
@interface MyTimerMiddle : NSObject
// target即是最终要连接的目标对象,注意:这里使用weak,原因后面会说明。
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval
target:(id)target
selector:(SEL)selector;
// 销毁timer
- (void)cleanup;
@end
timer
作为它的一个属性。
#import "MyTimerMiddle.h"
@interface MyTimerMiddle ()
@property (nonatomic, strong) NSTimer * timer;
@end
对外提供一个自定义的初始化方法
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval
target:(id)target
selector:(SEL)selector {
if (self = [super init]) {
self.target = target;
self.selector = selector;
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(fetchData:) userInfo:nil repeats:YES];
}
return self;
}
定时执行的任务,这里模拟了一个下载任务,3秒后回到主线程执行,这里是真正连接目标对象的地方。
- (void)fetchData:(NSTimer *)timer {
NSLog(@"开始下载.....");
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(self) sself = weakSelf;
if (!sself) {
return;
}
if (sself.target == nil) {
return;
}
id targrt = sself.target;
SEL selector = sself.selector;
if ([targrt respondsToSelector:selector]) {
[targrt performSelector:selector withObject:@{@"name": @"mike"}];
}
});
}
接下来是timer
的销毁方法。
- (void)cleanup {
[self.timer invalidate];
self.timer = nil;
}
使用的时候,我们在目标控制器里声明一个中间者类型的属性。
@interface MyViewController ()
@property (nonatomic, strong) MyTimerMiddle * timerMid;
@end
初始化中间类,注意,这里的taerget
指定为self
,因为之前声明target
属性的时候用的是weak
,因此这里中间类并没有强引用self
,从而避免了循环引用。showSome
即是目标对象最终要定时执行的任务。
self.timerMid = [[MyTimerMiddle alloc] initWithTimeInterval:5 target:self selector:@selector(showSome:)];
最后,我们需要在控制器销毁时对中间类进行清理工作。
- (void)dealloc {
NSLog(@"111");
[self.timerMid cleanup];
}