iOS 优化之NSTimer

定时器的应用

说到定时器,在项目中使用最多的可能就是NSTimer了,其实除了NSTimer,在iOS开发中,我们还有许多其他的方式可用来创建定时器。通过CADisplayLink或者GCD也可以是想强大的定时器功能。

缺点:

  1. NSTimer定时器,其实不是按时间间隔进行循环调用的,实际上在定时器注册到runloop中后runloop会设置一个一个的时间点进行调用,如果错过了,则定时器并不会延时条用,而是直接等待下一个时间点调用,所以台式不精准的。
  2. 子线程要手动开启RunLoop,这样创建的NSTimer才可以执行。
  3. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值