定时器在子线程中的循环引用

在上次详细的介绍了使用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张图片
NSTimer子线程循环引用.png
  • 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

你可能感兴趣的:(定时器在子线程中的循环引用)