在平时的开发中,计时器是一个常用的功能,iOS常用的计时器有三种,分别是NSTimer、CADisplayLink、GCD,下面来看一下它们的使用
NSTimer--受Runloop影响,相对不准确
NSTimer的创建方式有三种:
//方式一
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
此种方式需要将timer加到runloop才会执行
//方式二
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
创建后timer直接执行
//方式三
+ (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));
创建后timer直接执行,iOS 10后API
方式一的具体使用
在viewDidLoad中执行
- (void) timer_start_test {
if (self.timer == nil) {
self.timer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
}
在didMoveToParentViewController中执行
- (void)timer_stop_test {
//手动销毁计时器
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
方式二的具体使用
在viewDidLoad中执行
- (void) timer_start_test {
if (self.timer == nil) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
}
关闭方式与第一种相同
方式三的具体使用
- (void) timer_start_test {
if (self.timer == nil) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1. repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"lmf block:%@", [NSDate date]);
}];
}
}
关闭方式在dealloc中执行timer_stop_test方法
注:前两种创建方式需要手动销毁计时器,否则会引起循环引用,第三种创建方式需要在dealloc中停止计时器,否则计时器不会停止,具体实现代码见后文
CADisplayLink--与屏幕刷新频率同步的定时器
使用方式:
在viewDidLoad中开启,didMoveToParentViewController中关闭
- (void) displayLink_start_test {
if (self.displayLink_timer == nil) {
self.displayLink_timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerAction)];
//iOS 10之前frameInterval 之后preferredFramesPerSecond,设置N帧执行一次
self.displayLink_timer.preferredFramesPerSecond = 10;
//设置
[self.displayLink_timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
}
- (void) displayLink_stop_test {
[self.displayLink_timer invalidate];
self.displayLink_timer = nil;
}
GCD--不受Runloop影响,精确度最小可以精确到纳秒级
在viewDidLoad中开启,didMoveToParentViewController中关闭
- (void) gcd_start_test {
//GCD 创建的Timer,不受Runloop影响,精确度最小可以精确到纳秒级
if (self.gcd_timer == nil) {
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
[self timerAction];
});
//启动/唤醒
dispatch_resume(timer);
//需要强引用计时器,否则执行到},会被释放
self.gcd_timer = timer;
}
}
- (void) gcd_stop_test {
//挂起计时器
// dispatch_suspend(self.gcd_timer);
//取消计时器
dispatch_cancel(self.gcd_timer);
self.gcd_timer = nil;
}
NSTimer循环引用的解决方案
在合适的时机,关闭计时器
上面介绍计时器使用时,在didMoveToParentViewController里关闭计时器就是通过这种方案
通过中间者接受代理,这种方案需要在dealloc里停止计时器,否则self被销毁时,target找不到方法会导致崩溃
- (void)targetMove {
self.target = [NSObject new];
class_addMethod([self.target class], @selector(timerAction), class_getMethodImplementation([self class], @selector(timerAction)), "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self.target selector:@selector(timerAction) userInfo:nil repeats:YES];
}
通过proxy
首先自定义一个proxy类
@interface LProxy : NSProxy
@property (nonatomic, weak) id target;
@end
@implementation LProxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
然后将代理对象交给proxy,需要在对象销毁时,取消计时器
- (void)proxy_test {
_lproxy = [LProxy alloc];
_lproxy.target = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_lproxy selector:@selector(timerAction) userInfo:nil repeats:YES];
}
通过实现扩展Block实现
实现代码
#import <Foundation/Foundation.h>
typedef void (^ExecuteTimerBlock)(NSTimer *timer);
@interface NSTimer (Block)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti repeats:(BOOL)yesOrNo executeBlock:(ExecuteTimerBlock) block;
@end
#import "NSTimer+Block.h"
@implementation NSTimer (Block)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti repeats:(BOOL)yesOrNo executeBlock:(ExecuteTimerBlock) block {
NSTimer *timer = [self scheduledTimerWithTimeInterval:ti target:self selector:@selector(timerAction:) userInfo:[block copy] repeats:yesOrNo];
return timer;
}
+ (void)timerAction:(NSTimer *)timer {
ExecuteTimerBlock blcok = timer.userInfo;
if (blcok) {
blcok(timer);
}
}
调用
NSTimer *timer;
- (void)viewDidLoad {
[super viewDidLoad];
timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"自定义Timer Block");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)dealloc {
NSLog(@"%s", __FUNCTION__);
[timer invalidate];
}
生活如此美好,今天就点到为止。。。
网友评论