NSTimer
是iOS中常用的定時器,通常用來在固定時間間隔重復(fù)某個任務(wù)傲茄。使用起來也比較簡單更舞,但是一直以來存在一個問題,就是會造成循環(huán)引用入热,下面先來看下導(dǎo)致循環(huán)引用的用法。
假設(shè)現(xiàn)在有一個控制器疲迂。
@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秒執(zhí)行以下方法。
- (void)logNumber {
NSLog(@"22222");
}
看起來很簡單腰池,代碼也可以順利運行尾组,但是問題來了,timer
作為控制的屬性示弓,初始化時讳侨,控制器強引用timer
,在timer
的初始化方法中奏属,self
(即控制器)又作為target
被timer
強引用跨跨,毫無疑問,一個循環(huán)引用出現(xiàn)了囱皿,這將導(dǎo)致兩者都不能被釋放勇婴,也就是說,控制器在被推出的時候嘱腥,并不會執(zhí)行dealloc
方法耕渴,因此,下面的方法也就不會執(zhí)行齿兔。timer
自然也不會被釋放橱脸。
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
那要怎么解決呢础米?這里介紹一種方法,引入中間者添诉。
MyTimerMiddle
是一個中間類屁桑,它主要負(fù)責(zé)timer
的初始化,定時任務(wù)的觸發(fā)以及timer
的銷毀吻商,同時掏颊,也負(fù)責(zé)連接起最終要執(zhí)行任務(wù)的目標(biāo)對象。
@interface MyTimerMiddle : NSObject
// target即是最終要連接的目標(biāo)對象艾帐,注意:這里使用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;
}
定時執(zhí)行的任務(wù),這里模擬了一個下載任務(wù)事扭,3秒后回到主線程執(zhí)行捎稚,這里是真正連接目標(biāo)對象的地方。
- (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;
}
使用的時候今野,我們在目標(biāo)控制器里聲明一個中間者類型的屬性。
@interface MyViewController ()
@property (nonatomic, strong) MyTimerMiddle * timerMid;
@end
初始化中間類罐农,注意条霜,這里的taerget
指定為self
,因為之前聲明target
屬性的時候用的是weak
涵亏,因此這里中間類并沒有強引用self
宰睡,從而避免了循環(huán)引用。showSome
即是目標(biāo)對象最終要定時執(zhí)行的任務(wù)气筋。
self.timerMid = [[MyTimerMiddle alloc] initWithTimeInterval:5 target:self selector:@selector(showSome:)];
最后拆内,我們需要在控制器銷毀時對中間類進(jìn)行清理工作。
- (void)dealloc {
NSLog(@"111");
[self.timerMid cleanup];
}