1、产生原因

例如在viewController中创建一个NSTimer定时器:

  1. self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];

timer需要设置一个target,target对self产生了强引用,self又对timer强引用,这样就造成了一个循环引用,使得viewController和timer都无法释放。
image.png

2、解决方案

2.1、方案一:使用Block

在创建timer时,使用block执行任务:

  1. __weak typeof(self)weakSelf = self;
  2. self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
  3. [weakSelf timerTest];
  4. }];

这样创建后self对timer是强引用,timer对self是弱引用,不会造成循环引用。

*target方式传入weakSelf还是会循环引用,因为weakSelf不影响timer内部对self地址的引用方式。

image.png

2.2、方案二:消息转发

循环引用原因是ViewController和timer之间的相互强引用,可以使用一个中间变量,打断强引用关系,又能传递消息,就可以解决循环引用的问题,这里可以使用Proxy来实现,引用关系如下:
image.png
步骤一:创建proxy对象,将proxy的target设置成viewController(弱引用)

  1. @interface XLProxy : NSObject
  2. @property (nonatomic, weak) id target;
  3. + (instancetype)proxyWithTarget:(id)target;
  4. @end

步骤二:在创建timer时,将target设置成proxy对象

  1. - (void)initTimer {
  2. XLProxy *proxy = [XLProxy proxyWithTarget:self];
  3. self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(timerTest) userInfo:nil repeats:YES];
  4. }

步骤三:在proxy内部实现消息转发逻辑,将消息接收者设置成proxy的target(proxy继承于NSObject)

  1. + (instancetype)proxyWithTarget:(id)target {
  2. XLProxy *proxy = [[XLProxy alloc] init];
  3. proxy.target = target;
  4. return proxy;
  5. }
  6. // 消息转发接收者
  7. - (id)forwardingTargetForSelector:(SEL)aSelector {
  8. return self.target;
  9. }

为了提高效率,应该使用NSProxy类,NSProxy是专门用于消息转发的,不继承于NSObject,在接收到消息时,如果自己没有实现方法,不会去父类搜索方法,会直接进入消息转发调用流程,所以效率比较高,内部实现如下:

  1. + (instancetype)proxyWithTarget:(id)target {
  2. // NSProxy没有init方法
  3. XLProxy2 *proxy = [XLProxy2 alloc];
  4. proxy.target = target;
  5. return proxy;
  6. }
  7. // 接收到消息会直接进入方法签名方法
  8. - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
  9. return [self.target methodSignatureForSelector:sel];
  10. }
  11. // 调用方法
  12. - (void)forwardInvocation:(NSInvocation *)invocation {
  13. [invocation invokeWithTarget:self.target];
  14. }

2.3、方案三:GCD定时器

NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时,而GCD的定时器会更加准时,使用举例:

  1. // 获取队列
  2. dispatch_queue_t queue = dispatch_get_main_queue();
  3. // 创建定时器
  4. dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
  5. // 设置开始时间 2秒后
  6. dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
  7. // 设置定时器,参数:1、timer 2、开始时间 3、间隔(纳秒) 4、误差(纳秒)
  8. dispatch_source_set_timer(timer, start, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
  9. // 设置回调(block)
  10. dispatch_source_set_event_handler(timer, ^{
  11. NSLog(@"111");
  12. });
  13. // 启动定时器
  14. dispatch_resume(timer);
  15. // 保住定时器的命
  16. self.timer = timer;

也可以通过函数设置回调:

  1. // 设置回调(函数)
  2. dispatch_source_set_event_handler_f(timer, timerFire);
  3. void timerFire(void *param) {
  4. NSLog(@"222");
  5. }

如果设置的队列不是主队列,会自动进入子线程执行回调:
为了方便调用可以自己封装一个基于GCD的定时器 XLGCDTimer

  1. @interface XLGCDTimer : NSObject
  2. + (NSString *)execTask:(XLTaskBlock)task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async;
  3. + (NSString *)execTask:(id)target selector:(SEL)selecctor start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async;
  4. + (void)cancleTask:(NSString *)name;
  5. @end

*封装时需要保存创建的timer,并且要保证线程安全。