iOS中的定时器
oX01 NSTimer
常规的定时器,不需要精确的定时器间隔的操作,这个和runloop有关系
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (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));
- time 开头的是没有启动循环调用计划【需要调用fire】, 而scheduled这个是启动了调用计划。
因为timer持有这个对象, 所以,很容易造成循环引用。
一个是声明weak来进行破坏循环引用, 一个是使用block来间接破坏引用。
1、使用block来解除循环引用
使用我们block方式引用NSTimer这个方式为何能够解决循环引用问题? 这段代码将计时器所应执行的任务封装成“块”, 在迪奥哟共计时器函数时,, 把它所谓userInfo参数传进去。 该参数可用来存放“不透明值”, 只要计时器还有效, 就会一直保留它。 传入参数时候, 需要copy到堆上面保活。计时器现在的target 是NSTimer类对象, 这是一个单例, 因此计时器是否会保留它, 其实都无所谓。 此处依然有循环引用。 因为类对象无需回收,所以不用担心。
这里使用,先定义一个弱引用,再转化为强引用保持。 如果这个类对象释放,回收过程还会调用计时器的invalidate方法,这样的话,计时器就不会再执行任务了。此处使用weak应用还能零程序更加安全,因为这时开发者可能在编写dealloc时候忘了调用计时器的invalidate方法,从而导致计时器再次运行。 若发生此类情况,则块里的weakself 会变成nil。
NOTE: block 里面如果没有使用weak来修饰的话,这个是有问题的,还是有循环引用的,这个VC并不会调用dealloc方法,
eg: [self doStomthing] ;
这样的循环图:
[self doSomething 的时候造成的闭环, 所以,self要改成weak调用]
直接使用weak声明Timer也会造成持有的问题
@property (nonatomic, weak) NSTimer *timer; 即使有这样的声明,也是不行的,不会调用dealloc,因为timer还在执行,time强持有self,所有self不会被释放掉【这个时候self的引用计数不是0】。 这个时候timer一直在执行这个内容。
2、中间使用一个对象weak持有Timer, (和上面的block类似)
#import "HWWeakTimer.h"
@interface HWWeakTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;
@end
@implementation HWWeakTimerTarget
- (void) fire:(NSTimer *)timer {
if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
} else {
[self.timer invalidate];
}
}
@end
@implementation HWWeakTimer
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats {
HWWeakTimerTarget* timerTarget = [[HWWeakTimerTarget alloc] init];
timerTarget.target = aTarget;
timerTarget.selector = aSelector;
timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:timerTarget
selector:@selector(fire:)
userInfo:userInfo
repeats:repeats];
return timerTarget.timer;
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(HWTimerHandler)block
userInfo:(id)userInfo
repeats:(BOOL)repeats {
NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
if (userInfo != nil) {
[userInfoArray addObject:userInfo];
}
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(_timerBlockInvoke:)
userInfo:[userInfoArray copy]
repeats:repeats];
}
+ (void)_timerBlockInvoke:(NSArray*)userInfo {
HWTimerHandler block = userInfo[0];
id info = nil;
if (userInfo.count == 2) {
info = userInfo[1];
}
// or `!block ?: block();` @sunnyxx
if (block) {
block(info);
}
}
@end
第一个target方法的破解循环引用问题
第二个方法和yy库里面的block方式是一样的。 也会要注意block里面要用week修饰过的对象
3、 第2种的变种创建一个代理对象 :YYWeakProxy
使用实例 :
self.timer= [NSTimer scheduledTimerWithTimeInterval:0.1 target:[YYWeakProxy proxyWithTarget:self] selector:@selector(onScheduled:) userInfo:nil repeats:YES];
NOTE: [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 注意timer一般要加入commmonModes
oX02 CADisplayLink
CADisplayLink 要运行,需要手动添加runloop的模式, 如果不添加默认是pause,不会运行
//创建
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
// 添加runloop
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
// 移除runloop
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
- (void)invalidate; // 失效
@property(readonly, nonatomic) CFTimeInterval timestamp;
// 这次刷新的时间点【其值是基于一个随机值开始的递增值】
//可以两次时间相减之后得到时间间隔
@property(readonly, nonatomic) CFTimeInterval duration;
// 屏幕刷新一次的时候的时间间隔, 我们iOS1s刷新60次,所以这里的时间是: 1/60s
>selector的调用间隔时间计算方式是:间隔时间=duration×frameInterval。
@property(readonly, nonatomic) CFTimeInterval targetTimestamp
API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0));
// 暂停
@property(getter=isPaused, nonatomic) BOOL paused;
// 帧间隔时间
// 默认是1 ,表示1s刷新60次, 如果设置为2,表示1s内刷新30次 , 这个60次是和UI的刷新频率是一样的
@property(nonatomic) NSInteger frameInterval
API_DEPRECATED("preferredFramesPerSecond", ios(3.1, 10.0),
watchos(2.0, 3.0), tvos(9.0, 10.0));
@property(nonatomic) NSInteger preferredFramesPerSecond
API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0));
一般用于做UI界面的不停重绘,比如自定义动画引擎或者视频播放的渲染。
NSTimer 和CADisplayLink区别
CADisplayLink
使用上和NSTimer没有太大差别,是要是添加runloop这里小小差别。 还有就是这个主要用在和UI刷新相关的。eg: UI上面的动画绘制等等。
oX03 【精确定时器】
dispatch_source_t的定时器不受RunLoop影响,而且dispatch_source_t是系统级别的源事件,精度很高,系统自动触发。
首先明确:dispatch_source_t源事件有一种类型就是DISPATCH_SOURCE_TYPE_TIMER——用来计时的
// 示例代码
//(1)创建源事件
//(2)设置定时器事件
//(3)设置事件触发的回调
//(4)运行
- (void)testGcdSource{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
// 处理的内容
});
dispatch_resume(_timer);
}
NSTimer、 CADisplayLink、以及GCD里面的source的原理和区别。
NSTimer都是和Runloop有很大的关系的, runloop是运行时的系统 ;runloop
dispatch_source和runloop是没有关系的,系统触发的
使用方面:
和UI有关的的使用CADisplayLink , 如果要去精确使用dispatch_source, 其他的使用NSTimer 。
NSTimer 和CFRunLoopTimerRef (定时源)
CFRunLoopTimerRef 和NSTimer是Tool-Free Bridged 关系。 它包含一个时间长度和一个回调(函数指针)。 当其加入到runloop时,runloop会注册对应的时间点,当时间到时,Runloop会被唤醒以执行那个回调。
NSTimer 默认Runloop是default