1、产生原因
例如在viewController中创建一个NSTimer定时器:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
timer需要设置一个target,target对self产生了强引用,self又对timer强引用,这样就造成了一个循环引用,使得viewController和timer都无法释放。
2、解决方案
2.1、方案一:使用Block
在创建timer时,使用block执行任务:
__weak typeof(self)weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
这样创建后self对timer是强引用,timer对self是弱引用,不会造成循环引用。
*target方式传入weakSelf还是会循环引用,因为weakSelf不影响timer内部对self地址的引用方式。
2.2、方案二:消息转发
循环引用原因是ViewController和timer之间的相互强引用,可以使用一个中间变量,打断强引用关系,又能传递消息,就可以解决循环引用的问题,这里可以使用Proxy来实现,引用关系如下:
步骤一:创建proxy对象,将proxy的target设置成viewController(弱引用)
@interface XLProxy : NSObject
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
步骤二:在创建timer时,将target设置成proxy对象
- (void)initTimer {
XLProxy *proxy = [XLProxy proxyWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(timerTest) userInfo:nil repeats:YES];
}
步骤三:在proxy内部实现消息转发逻辑,将消息接收者设置成proxy的target(proxy继承于NSObject)
+ (instancetype)proxyWithTarget:(id)target {
XLProxy *proxy = [[XLProxy alloc] init];
proxy.target = target;
return proxy;
}
// 消息转发接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
为了提高效率,应该使用NSProxy类,NSProxy是专门用于消息转发的,不继承于NSObject,在接收到消息时,如果自己没有实现方法,不会去父类搜索方法,会直接进入消息转发调用流程,所以效率比较高,内部实现如下:
+ (instancetype)proxyWithTarget:(id)target {
// NSProxy没有init方法
XLProxy2 *proxy = [XLProxy2 alloc];
proxy.target = target;
return proxy;
}
// 接收到消息会直接进入方法签名方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
// 调用方法
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
2.3、方案三:GCD定时器
NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时,而GCD的定时器会更加准时,使用举例:
// 获取队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置开始时间 2秒后
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
// 设置定时器,参数:1、timer 2、开始时间 3、间隔(纳秒) 4、误差(纳秒)
dispatch_source_set_timer(timer, start, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 设置回调(block)
dispatch_source_set_event_handler(timer, ^{
NSLog(@"111");
});
// 启动定时器
dispatch_resume(timer);
// 保住定时器的命
self.timer = timer;
也可以通过函数设置回调:
// 设置回调(函数)
dispatch_source_set_event_handler_f(timer, timerFire);
void timerFire(void *param) {
NSLog(@"222");
}
如果设置的队列不是主队列,会自动进入子线程执行回调:
为了方便调用可以自己封装一个基于GCD的定时器 XLGCDTimer :
@interface XLGCDTimer : NSObject
+ (NSString *)execTask:(XLTaskBlock)task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async;
+ (NSString *)execTask:(id)target selector:(SEL)selecctor start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async;
+ (void)cancleTask:(NSString *)name;
@end
*封装时需要保存创建的timer,并且要保证线程安全。