在上次详细的介绍了使用NSTimer、CADisplayLink、GCD定时器时会造成循环引用,但是都是在主线程上造成的循环引用,本篇主要验证子线程使用NSTimer、CADisplayLink、GCD定时器时如何避免循环引用
一、NSTimer
- 1、中间代码RevanProxy
#import
@interface RevanProxy : NSObject
/**
构造中间件
*/
+(instancetype)revan_proxy:(id)target;
@end
#import "RevanProxy.h"
@interface RevanProxy ()
/** target */
@property (nonatomic, weak) id target;
@end
@implementation RevanProxy
/**
构造中间件
*/
+(instancetype)revan_proxy:(id)target {
RevanProxy *proxy = [[RevanProxy alloc] init];
proxy.target = target;
return proxy;
}
/**
消息转发
@param aSelector 消息
@return 执行消息的对象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
- 2、测试代码
#import "ViewController.h"
#import "RevanProxy.h"
@interface ViewController ()
/** NSTimer */
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[RevanProxy revan_proxy:self] selector:@selector(timerFun:) userInfo:@"参数" repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
//如果是子线程还需要启动runloop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
- (void)timerFun:(NSTimer *)timer {
NSLog(@"%s --%@--%@", __func__, [NSThread currentThread], timer.userInfo);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
@end
- 3、打印输出
2018-07-25 23:59:30.729063+0800 09-GCD定时器[37759:2289444] -[ViewController timerFun:] --{number = 4, name = (null)}--参数
2018-07-25 23:59:31.727838+0800 09-GCD定时器[37759:2289444] -[ViewController timerFun:] --{number = 4, name = (null)}--参数
2018-07-25 23:59:32.725257+0800 09-GCD定时器[37759:2289444] -[ViewController timerFun:] --{number = 4, name = (null)}--参数
- 4、问题
- 定时器当加入到主线程时,这样的代码是不会造成循环引用的,但是当定时器加入到子线程时,会造成循环引用,就想现在这样
解决NSTimer子线程循环引用
在主线程中不会造成循环引用,但是子线程会造成循环引用,问题应该是出在子线程问题上。在定时器释放时必须要调用invalidate方法,这个方法会做一些释放self、block、RunLoop等释放资源的工作,而且释放RunLoop只能释放和定时器同一个线程的RunLoop。
由于现在定时器加入到子线程,所以子线程的RunLoop会强引用着定时器,造成定时器无法释放,所以可以用一个属性保持当前定时器加入的子线程的RunLoop,在点击back时在viewDidDisappear:方法中手动释放子线程中的RunLoop
- 1、测试代码
#import "ViewController.h"
#import "RevanProxy.h"
@interface ViewController ()
/** NSTimer */
@property (nonatomic, strong) NSTimer *timer;
/** 子线程RunLoop */
@property (nonatomic, strong) NSRunLoop *subThreadRunLoop;
@end
@implementation ViewController
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
//停止RunLoop
CFRunLoopStop([self.subThreadRunLoop getCFRunLoop]);
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[RevanProxy revan_proxy:self] selector:@selector(timerFun:) userInfo:@"参数" repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
//如果是子线程还需要启动runloop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
- (void)timerFun:(NSTimer *)timer {
NSLog(@"%s --%@--%@", __func__, [NSThread currentThread], timer.userInfo);
self.subThreadRunLoop = [NSRunLoop currentRunLoop];
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
@end
- 打印输出
2018-07-26 00:40:37.503734+0800 09-GCD定时器[38302:2322594] -[ViewController timerFun:] --{number = 4, name = (null)}--参数
2018-07-26 00:40:38.502150+0800 09-GCD定时器[38302:2322594] -[ViewController timerFun:] --{number = 4, name = (null)}--参数
2018-07-26 00:40:39.503694+0800 09-GCD定时器[38302:2322594] -[ViewController timerFun:] --{number = 4, name = (null)}--参数
2018-07-26 00:40:39.679073+0800 09-GCD定时器[38302:2322534] -[ViewController dealloc]
二、GCD
- 测试代码
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t gcdTimer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//1.队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//2.创建定时器
self.gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.设置时间
// start 秒后开始执行
uint64_t start = 2.0;
// 每隔interval执行
uint64_t interval = 1.0;
dispatch_source_set_timer(self.gcdTimer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
//4.设置回调
__weak typeof(self)weakSelf = self;
dispatch_source_set_event_handler(self.gcdTimer, ^{
[weakSelf timerFire];
});
//5.启动定时器
dispatch_resume(self.gcdTimer);
}
- (void)timerFire {
NSLog(@"%s - %@", __func__, [NSThread currentThread]);
}
- (void)dealloc {
self.gcdTimer = nil;
NSLog(@"%s", __func__);
}
@end
- 打印输出
2018-07-26 01:00:51.910084+0800 09-GCD定时器[38680:2341995] -[ViewController timerFire] - {number = 4, name = (null)}
2018-07-26 01:00:52.909810+0800 09-GCD定时器[38680:2341995] -[ViewController timerFire] - {number = 4, name = (null)}
2018-07-26 01:00:53.908858+0800 09-GCD定时器[38680:2341995] -[ViewController timerFire] - {number = 4, name = (null)}
2018-07-26 01:00:54.908994+0800 09-GCD定时器[38680:2341995] -[ViewController timerFire] - {number = 4, name = (null)}
2018-07-26 01:00:55.910082+0800 09-GCD定时器[38680:2341995] -[ViewController timerFire] - {number = 4, name = (null)}
2018-07-26 01:00:56.908988+0800 09-GCD定时器[38680:2341995] -[ViewController timerFire] - {number = 4, name = (null)}
2018-07-26 01:00:57.003828+0800 09-GCD定时器[38680:2341934] -[ViewController dealloc]
三、小结
- GCD定时器子线程和主线程都可以很方便的执行,所以推荐使用GCD
- 定时器Demoe