1、NsTimer方式
会存在延迟情况。此种方法把timer加入runloop中执行。如果runloop正在执行连续性任务,则需要等待任务结束
__weak typeof(self)wSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
wSelf.countNum ++;
NSLog(@"count is ...%d",wSelf.countNum);
}];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
子线程启动timer可以参考下这篇文章说的不错。。
http://08643.cn/p/d4589134358a
2、dispatch方式
dispatch_source_t精度很高,系统自动触发,系统级别源
__block int timeout=120; //倒计时120秒
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _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, ^{
if(timeout<=0){ //倒计时结束,关闭
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
//设置界面的按钮显示 根据自己需求设置
});
}else{
int minutes = timeout / 60;
int seconds = timeout % 60;
NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒",minutes, seconds];
dispatch_async(dispatch_get_main_queue(), ^{
//设置界面的按钮显示 根据自己需求设置
NSLog(@"countingTime:%@",strTime);
});
timeout—;
}
});
dispatch_resume(_timer);
3、CADisplayLink方式(会和timer这样调用方式一样,存在循环引用)
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerMethod)];
[_displayLink setPreferredFramesPerSecond:1];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//销毁定时器
//[_displayLink invalidate];
//_displayLink = nil;
相比nstimer来说
原理不同
CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。
NSTimer以指定的模式注册到runloop后,每当设定的周期时间到达后,runloop会向指定的target发送一次指定的selector消息。
时间设定不同
iOS设备的屏幕刷新频率(FPS)是60Hz,因此CADisplayLink的selector默认调用周期是每秒60次,这个周期可以通过frameInterval属性设置,CADisplayLink的selector每秒调用次数=60/frameInterval。比如当frameInterval设为2,每秒调用就变成30次。因此,CADisplayLink周期的设置方式略显不便。(frameInterval已过期。改为了setPreferredFramesPerSecond直接=selector每秒调用次数。。)
使用上来说
CADisplayLink使用场合相对专一,适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。
引发问题
nstimer的循环引用
首先定义新页面,页面内创建变量timer 同时delloc方法内部销毁timer并置空。。
@property(nonatomic,strong) NSTimer * timer;/**ins*/
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor purpleColor];
__weak typeof(self)wSelf = self;
1、block的调用方式不会引发循环引用。。
self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
wSelf.countNum ++;
NSLog(@"count is ...%d",wSelf.countNum);
}];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
2、此种方法引发循环引用。即便使用weakSelf依旧如此。self本身不会释放。。
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
- (void)dealloc
{
[self.timer invalidate];
self.timer = nil;
NSLog(@"self.timer invalidate && self.timer = nil %s",__func__);
}
- (void)stopTimer{
self.timer = nil;
[self.timer invalidate];
}
可见当timer指定target=self的时候,引发了循环引用。此时状态是
vc持有了timer;
timer内部持有Vc;
runloop持有了timer
所以即便self写成wself也没有作用
解决方案
最好的方式是我们预知某些关闭页面情况下,直接调用stopTimer方法,销毁timer,从runloop中移除timer,比如viewWillDisappear内部。
除此之外,还可以使用代理对象。。
@interface TimerProxy : NSObject
+ (instancetype) proxyWithTarget:(id)target;
@property (weak, nonatomic) id target; //若引用vc类目
@end
@implementation LJProxy
+ (instancetype) proxyWithTarget:(id)target
{
TimerProxy *proxy = [[TimerProxy alloc] init];
proxy.target = target;
return proxy;
}
//因为timer指定了TimerProxy 为代理类目,所以势必会到此种寻找调用的selector。。
所以这里直接交由原来类处理,找到其内部调用的方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
此时关系如图所示:
此时vc没有强引用。。直接pop没有问题。?;嶂葱衪imer的销毁方法从而全部释放。。。
除了自己的代理类目,还可以直接用系统代理NSProxy
修改代码直接继承自NSProxy
@interface TimerProxy: NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation TimerProxy
+ (instancetype)proxyWithTarget:(id)target {
TimerProxy*proxy = [TimerProxyalloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
我们看到实现方法不同。。这里需要介绍下nsproxy
runtime学习过程中,查找一个方法。需要直接通过缓存、方法列表各种查找。然后走消息转发。。调用resolveInstanceMethod、forwardTarget、methodSignatureForSelector、forwardInvocatio等一系列处理。。。但是nsproxy会直接的走到methodSignatureForSelector、forwardInvocatio来处理消息转发。。更加的效率高效。。。
文章参考
http://08643.cn/p/a5353deac41b
http://08643.cn/p/69e90ce06475
http://08643.cn/p/d4589134358a