首要思考下为什么只需提到 NSTimer 就会与循环引证挂钩?首要的原因是:NSTimer 目标会强引证传入的 target,所以 iOS 10 今后,苹果给了咱们一个 NSTimer 的 block 为参数的 API,还特意嘱咐了咱们:- parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references,帮助咱们避免循环引证。

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (NS_SWIFT_SENDABLE ^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

 除了循环引证,还有一条咱们要谨记在心,当咱们创立了一个 repeats 参数为 True 的 NSTimer 目标后,当不需要它时一定要调用 - (void)invalidate; 终止定时器,不然定时器会一向履行下去,定时器目标不会开释,那么即使 target 没有强引证定时器目标(没有循环引证),target 也会由于一向被定时器目标强引证而得不到开释,此刻 target 便内存泄漏了。

invalidate 函数很重要,不仅是用来停止定时器的,同时也会使定时器目标终止对 target 的强引证。若是定时器目标与 target 有彼此引证的话,相当于自动解除了引证环。例如最粗陋的方法:咱们在 viewDidDisappear 函数中自动调用定时器的 invalidate 函数,那么咱们的 ViewController 还是能正常履行 dealloc 并开释的。

 这儿还有一个有意思的点,当 repeats 参数运用 false 时,不论 target 与定时器目标有没有引证环,target 都能够在定时器履行后得到开释,定时器目标不再强引证 target,这个机制能够让咱们延伸目标的生命周期到指定的时间。

 1️⃣:凭借中间层,NSObject 子类:

#import "TimerWrapper.h"
typedef void(^TimerBlock)(void);
@interface TimerWrapper ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, copy) TimerBlock block;
@end
@implementation TimerWrapper
- (void)startTimer:(void(^)(void))block {
    self.block = block;
    if (self.timer != nil) {
        [self.timer invalidate];
        self.timer = nil;
    }
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
}
- (void)timerAction:(NSTimer *)timer {
    if (self.block) {
        self.block();
    }
}
- (void)stopTimer {
    if (self.timer == nil) {
        return;
    }
    [self.timer invalidate];
    self.timer = nil;
}
- (void)dealloc {
    NSLog(@"TimerWrapper dealloc");
}
@end

 例如在 ViewController 中运用一个 TimerWrapper 目标敞开定时器,然后在 ViewController 的 dealloc 函数中调用 stopTimer 函数,自动停止定时器。

 2⃣️:凭借中间层,NSProxy 子类:

#import "TimerProxy.h"
@interface TimerProxy ()
@property (nonatomic, weak) id target;
@end
@implementation TimerProxy
+ (instancetype)proxyWithTarget:(id)target {
    TimerProxy *proxy = [TimerProxy alloc];
    proxy.target = target;
    return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end

TimerProxy 弱引证一个 target,这个 target 可所以咱们的 ViewController。在 TimerProxy 内部,通过 forwardInvocationTimerProxy 目标接收的音讯转发到 target 中去,即把定时器的回调转回到指定的 target 中去。这样咱们的 ViewController 仅被 TimerProxy 弱引证,能够得正常开释,然后在它的 dealloc 函数中,终止定时器,破开定时器目标和 TimerProxy 目标的强引证,双方都得到了正常的终止和开释。

 2️⃣:运用 block,iOS 10 今后系统为咱们提供了 Block 方法的定时器,那么 iOS 10 之前呢,其实咱们也能够手动实现一个:

#import "NSTimer+Test.h"
@implementation NSTimer (Test)
+ (NSTimer *)cus_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer * _Nonnull))block {
    return [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(handle:) userInfo:[block copy] repeats:repeats];
}
+ (void)handle:(NSTimer *)timer {
    void(^block)(NSTimer * _Nonnull) = timer.userInfo;
    if (block != nil) {
        block(timer);
    }
}
- (void)dealloc {
    NSLog(@"timer dealloc 履行!");
}
@end

 通过分类给 NSTimer 增加一个创立 NSTimer 目标的类函数,创立 NSTimer 时咱们通过 userInfo 参数传递一个自己的自定义 block,然后 NSTimer 目标创立时 target 传递的是 NSTimer 类目标,由于类目标完全不需要考虑开释问题,它是全局唯一且不需要开释的,所以咱们完全不必考虑咱们运用自己的目标作为 target 时引发的强引证问题。最终当 NSTimer 的 selector 履行时,咱们从 userInfo 中读取到咱们传递到 block 进行履行,完美避开了引证环问题,实现了和 iOS 10 今后相似的 Block 方法的 NSTimer 创立。

 还有 CADisplayLink 也是和 NSTimer 相同会强引证传入的 target,咱们能够运用相同的方法解除它们的循环引证。

 由于 NSTimer 的回调依赖于 RunLoop 中的 timerPort,当 RunLoop 中任务量大和 RunLoop Mode 切换时会导致一些 NSTimer 定时禁绝问题。

参阅链接

参阅链接: