定时器的应用
说到定时器,在项目中使用最多的可能就是NSTimer了,其实除了NSTimer,在iOS开发中,我们还有许多其他的方式可用来创建定时器。通过CADisplayLink或者GCD也可以是想强大的定时器功能。
缺点:
- NSTimer定时器,其实不是按时间间隔进行循环调用的,实际上在定时器注册到runloop中后runloop会设置一个一个的时间点进行调用,如果错过了,则定时器并不会延时条用,而是直接等待下一个时间点调用,所以台式不精准的。
- 子线程要手动开启RunLoop,这样创建的NSTimer才可以执行。
- NSTimer容易发生内存泄漏,并且一个应用程序中,如果激活多个定时器的话很消耗性能
中心化管理NSTimer定时器
创建一个Task类,将它作为定时器的任务类,使用继承于NSObject,在其中声明如下属性和方法:
//Task.h文件
@interface Task : NSObject
- (instancetype)initWithTimer:(NSUInteger)time handler: (void(^)(void)) hander;
//标志
@property (nonatomic, strong, readonly) NSString * taskID;
//时间单位为 1/60秒
@property (nonatomic, assign) NSUInteger timeI;
//需要执行的动作
@property (nonatomic, copy)void(^event)(void);
@end
//Task.m文件
#import "Task.h"
@implementation Task
- (instancetype)initWithTimer:(NSUInteger)time handler: (void(^)(void)) hander {
self = [super init];
if (self) {
self.timeI = time;
self.event = hander;
_taskID = [NSUUID UUID].UUIDString;
}
return self;
}
@end
在创建一个TimerManager的类,使其继承于NSObject,这个类的作用是统一管理整个工程的定时任务,我们将其设计为单例,接口如下:
//TimerManager.h文件
@class Task;
@interface TimerManager : NSObject
//单例
+ (instancetype)shareManager;
//运行定时器任务
- (void)runTask:(Task *) task;
//取消定时器任务
- (void)cancelTaskWithID: (NSString *) taskID;
@end
//TimerManager.m文件
#import "TimerManager.h"
#import "Task.h"
@interface TimerManager()
@property (nonatomic, strong) NSMutableArray *taskArray;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TimerManager
+ (instancetype)shareManager {
static dispatch_once_t onceToken;
static TimerManager *manager = nil;
dispatch_once(&onceToken, ^{
if (!manager) {
manager = [[TimerManager alloc] init];
}
});
return manager;
}
- (instancetype)init
{
self = [super init];
if (self) {
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode: NSRunLoopCommonModes];
}
return self;
}
- (void)runTask:(Task *)task {
for (Task *t in self.taskArray) {
if ([t.taskID isEqualToString:task.taskID]) {
return;
}
}
[self.taskArray addObject:task];
}
- (void)cancelTaskWithID:(NSString *)taskID {
for (int i = (int)self.taskArray.count - 1; i >= 0; i--) {
if ([[self.taskArray[i] taskID] isEqualToString:taskID]) {
[self.taskArray removeObjectAtIndex:i];
}
}
}
- (NSMutableArray *)taskArray {
if (!_taskArray) {
_taskArray = [NSMutableArray array];
}
return _taskArray;
}
- (NSTimer *)timer {
if (!_timer) {
static int index = 0;
_timer = [NSTimer scheduledTimerWithTimeInterval:1 / 60 repeats:YES block:^(NSTimer * _Nonnull timer) {
if (index == 59) {
index = 0;
}
for (Task *t in self.taskArray) {
if (index % t.timeI == 0) {
t.event();
}
}
index++;
}];
}
return _timer;
}
@end
这样,当我们需要使用定时任务时,将任务加入此中管理单例即可,无论我们有多少个任务,整个工程都只有一个定时器运行。Demo如下
#import "ViewController.h"
#import "Task.h"
#import "TimerManager.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *taskID;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Task *t1 = [[Task alloc] initWithTimer:60 handler:^{
NSLog(@"DDDD");
}];
self.taskID = t1.taskID;
Task *t2 = [[Task alloc] initWithTimer:30 handler:^{
NSLog(@"DDDD2");
}];
[[TimerManager shareManager] runTask:t1];
[[TimerManager shareManager] runTask:t2];
NSLog(@"%@", t2.taskID);
// Do any additional setup after loading the view.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%@", self.taskID);
[[TimerManager shareManager] cancelTaskWithID:self.taskID];//只关闭了task1
}
@end
CADisplayLink
它也是一种定时器,并且在很多自定义动画中,CADisplayLink有这比NSTimer更好地表现,CADisplayLink调用频率和设备的刷新频率一致。
由于CADisplayLink这个特性我们可以用来检测应用程序的帧率。
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(myDisplayLink)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void) myDisplayLink {
static NSTimeInterval curr = 0;
if (curr != 0) {
NSLog(@"%f", 1 /(self.link.timestamp - curr));
}
curr = self.link.timestamp;
}
注意它和NSTimer一样,当我们不再需要使用CADisplayLink定时器的死后我们需要手动设置其失效方法如下
- (void)invalidate;
GCD
熟悉GCD的开发者会经常使用下面的法法来处理延迟任务:
//延迟0.5秒执行不受RunLoop影响
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// code to be executed after a specified delay
});
GCD定时器
//
// ViewController.m
// CenterManagerTimer
//
// Created by bruceyao on 2021/3/30.
//
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设置频率
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 1 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"Hello");
});
// 激活
dispatch_resume(self.timer);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%@", self.taskID);
[[TimerManager shareManager] cancelTaskWithID:self.taskID];
}
- (void) myDisplayLink {
static NSTimeInterval curr = 0;
if (curr != 0) {
NSLog(@"%f", 1 /(self.link.timestamp - curr));
}
curr = self.link.timestamp;
}
@end