NSTimer
@property (strong, nonatomic) NSTimer *timer;
- 方式1创建
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
- 方式2创建
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
- 控制器销毁的时候销毁计时器
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
[self.timer invalidate];
}
- 运行时发现方式2创建的计时器并不能及时销毁引发了循环引用的问题。
CADisplayLink
- 保证调用频率和屏幕的刷帧频率一致,60FPS
- CADisplayLink没有Block的调用方式仅有这样的创建方式,创建完成后加入当前线程的runloop中执行
@property (strong, nonatomic) CADisplayLink *link;
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
[self.timer invalidate];
}
- 运行时发现这样也会引发循环引用的问题。
循环引用
- 控制器ViewController 强引用了 计时器
- 计时器中的target强引用了viewController
- 引发循环引用
解决方案
- 我们这做一个另一个对象,
- viewcontroller->timer->OtherObject
- OtherObject弱引用ViewController
- OtherObject 通过消息转发机制,正常情况下本来由OtherObject执行方法的让OtherObject弱引用着的ViewController执行
CADisplayLink、NSTimer使用注意
-
CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
-
解决方案
- 使用block
OtherObject
- OtherObject类
@interface MJProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
#import "MJProxy.h"
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
MJProxy *proxy = [[MJProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
- 调用
self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
- 原理其实就是runtime的消息机制完成的
- 1.计时器打开让OtherObject完成关联,计时器强引用OtherObject
- 2.OtherObject成员属性传入了ViewController,weak修饰弱引用ViewController
- 3.计时器放入Runloop中开始调用
- 4.本来应该由OtherObject调用linkTest的,OtherObject经过消息发送,方法解析后都没有找到这个方法并调用,进入了消息转发,- (id)forwardingTargetForSelector:(SEL)aSelector,让这个别的类看看有没有这个linkTest方法,返回了ViewController
- 5.ViewController调用方法。
使用代理对象(NSProxy)
- NSProxy其本质上和NSObject是一样的但是这个类专门用来做消息转发的,这个类在做消息转发时,直接就去找这个方法了- (id)forwardingTargetForSelector:(SEL)aSelector,看看是哪个类。所以内部实现比用NSObject来做消息转发更加快速。
#import <Foundation/Foundation.h>
@interface MJProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
#import "MJProxy.h"
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
MJProxy *proxy = [MJProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
GCD计时器
-
NSTimer本质上是放到runloop中才能运行的,同时NSTimer在runloop中优先级是比较低的。
-
NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
- 当runloop中sources0中的其他事件完成后才能去响应timer中的事件去完成计时功能
- 当我们定义每0.5秒执行一次计时时,如果其他任务的事件超过了0.5秒,假如0.6秒,所以计时器是在0.6秒后才执行的。会引发计时时间不准确。
- 解决方案1:重新开辟一个线程,runloop中单独放这个计时器来完成计时工作
- 解决方案2:使用GCD计时器
-
而GCD的定时器会更加准时
— 上源码
#import <Foundation/Foundation.h>
@interface MJTimer : NSObject
+ (NSString *)execTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (void)cancelTask:(NSString *)name;
@end
#import "MJTimer.h"
@implementation MJTimer
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 队列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : 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(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器的唯一标识
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重复的任务
[self cancelTask:name];
}
});
// 启动定时器
dispatch_resume(timer);
return name;
}
+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!target || !selector) return nil;
return [self execTask:^{
if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
+ (void)cancelTask:(NSString *)name
{
if (name.length == 0) return;
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER); //利用并发数来加锁
dispatch_source_t timer = timers_[name];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_); //解锁
}
@end
- 调用
@property (copy, nonatomic) NSString *task;
// 接口设计
self.task = [MJTimer execTask:self
selector:@selector(doTask)
start:2.0
interval:1.0
repeats:YES
async:NO];
[MJTimer cancelTask:self.task];
这个task就是这个计时器的标识来用取出数组中的计时器,然后控制计时器的关闭和判断。
- 网上资料也挺多的就不赘述了。