关于NSTimer的介绍
1、最近在总结知识、好久没记录了,今天抽空记录下NSTimer的使用,首先我们先看下App官方API文档中解释的NSTimer
### Summary
A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.
### Discussion
第一段:
Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
首先看这段的解释,NSTimer的工作依赖NSRunLoop,并且RunLoop保持强持有当前的Timer, 这个时候当你把Timer添加到当前的RunLoop后,你当前的Timer就被Run Loop强持有了
To use a timer effectively, you should be aware of how run loops operate. See [Threading Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i) for more information.
A timer is not a real-time mechanism. If a timer’s firing time occurs during a long run loop callout or while the run loop is in a mode that isn't monitoring the timer, the timer doesn't fire until the next time the run loop checks the timer. Therefore, the actual time at which a timer fires can be significantly later. See also [Timer Tolerance](applewebdata://43EE1961-4719-4FAB-9500-13E376222651#1667624).
[NSTimer](apple-reference-documentation://hcaODPrPmP) is toll-free bridged with its Core Foundation counterpart, [CFRunLoopTimerRef](apple-reference-documentation://hcPs68Qze5).
这段讲述了Timer并不是十分精确的,他需要依赖runloop的处理事件循环周期,所以有一个容忍的公差度
NSTimer是如何造成的当前VC无法释放的
如上图所示,当你使用下列方式Target-Action初始话的时候,造成的当前self无法释放,原因当前的NSRunLoop强持有的Timer,而timer又被self强持有,所以Timer无法释放,self也无法释放,
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(selector) userInfo:id repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
如何解决上边这种方法定义的Timer呢?就是我需要调用的【self.timer invalidate】方法,下面我们看下官方文档给出的解释
特别是图中的标出来的这句,意思是当你调用invalidate的时候,会打破runLoop强引用Timer,也同时打破Taget对Timer的强持有,这个时候 在看(1587377415988.jpg)此图剩余self对timer单向强持有了,所谓的循环引用也就被打破了,所以打破的重重之重是需要调用invalidate方法。
总结:
当我们使用初始化方式是Target-Action这种方式的初始化确实是存在循环引用的也就是下列的方法
需要代码添加到当前的RunLoop中
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
系统自己给你添加到当前的RunLoop中并且是Defalut Mode模式 参考API的注释
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
需要代码添加到当前的RunLoop中
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
系统自己给你添加到当前的RunLoop中并且是Defalut Mode模式 参考API的注释
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
第二种初始化 使用了带有Block的事件回调函数进行的处理,初始化函数,可能苹果意思到定时器存在的循环引用的缺陷,在iOS 10系统以后使用了这种Block回调的 就不存在循环引用了,此时也可以再dealloc函数中调用 invalidate 函数就不会存Target-Action 这种形式初始化的定时器的无法调用的问题了
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/// Initializes a new NSTimer object using the block as the main body of execution for the timer. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
/// - parameter: fireDate The time at which the timer should first fire.
/// - parameter: interval The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - 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
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
介绍完循环引用的问题了,我们看下使用Taregt-Action 这种形式,我们一般是需要选择合适的时机去调用invalidate解除下循环引用让定时器无效,但是这个时机的选择比较不确定,需要根据具体的业务需求如pop出页面的时候,dismiss调页面时候。主动调用invalidate函数
所以我们不想管他什么时机调用,我们不想手动调用了,我们只关注当前VC释放的时候定时器就无效掉,于是我们需要借助一个中间类去帮我们自动实现这种需求
类方法:
创建一个继承NSObject的子类TempTarget,并创建开启计时器的方法。
TempTarget .h文件
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *tempTimer;
@property (nonatomic, weak) id tempTarget;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats;
TempTarget .m文件
@implementation TempTarget
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
TempTarget *target = [[TempTarget alloc] init];
target.tempTarget = tempTarget;
target.selector = selector;
target.tempTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:target selector:@selector(timerSelector:) userInfo:userInfo repeats:repeats];
return target.tempTimer;
}
- (void)timerSelector:(NSTimer *)tempTimer {
if (self.tempTarget && [self.tempTarget respondsToSelector:self.selector]) {
[self.tempTarget performSelector:self.selector withObject:tempTimer.userInfo];
}else {
[self.tempTimer invalidate];
}
}
@end
VC调用
self.timer = [TempTarget scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerStart:) userInfo:nil repeats:YES];
注意
VC强引用timer,因为timer的target是TempTarget实例,所以timer强引用TempTarget实例,而TempTarget实例弱引用VC,解除循环引用。看TempTarget.h 文件两个所引用属性,其实这种方法本质还是存在循环医用的问题的,根本解决循环引用还是主动调用invalidate方法