iOS底层-定时器
CADisplayLink、NSTimer使用注意
-
NStimer与CADisplayLink初始化示例
#import "ZHTestViewController.h" @interface ZHTestViewController () //@property (nonatomic,strong) CADisplayLink *playLink; @property (nonatomic,strong) NSTimer *timer; @end @implementation ZHTestViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = UIColor.blueColor; /*********CADisplayLink**********/ //每隔一段时间掉用一次方法 //调用时间不用设置,调用时间间隔跟屏幕刷帧频率一样。 //displayLinkWithTarget: selector: 该方法会对target捕捉,造成循环引用 // self.playLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)]; // [self.playLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; /*********NSTimer**********/ //scheduledTimerWithTimeInterval也对target有一个强引用,导致循环引用 self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES]; } - (void)linkTest { NSLog(@"%s",__func__); } - (void)timerTest { NSLog(@"%s",__func__); } -(void)dealloc{ // [self.playLink invalidate]; [self.timer invalidate]; } @end
- 发现问题,无论是NSTimer还是CADisplayLink,back到上一个控制器时,都不能正常的停止定时器,而且当前控制器也不会销毁,说明产生了循环引用
- 经过分析分别是:
displayLinkWithTarget: selector:
与scheduledTimerWithTimeInterval
都对target进行了强引用,导致循环引用 -
对于NSTeimer可以用另外一种创建方式来解决这个问题
//这个只能在block中使用 __weak typeof(self) weakself = self; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { [weakself timerTest]; }];
- 但是CADisplayLink呢? 而且NSTimer的非block方式就不能解决了吗?
-
循环引用分析以及解决办法如下:
- 解决办法
- 引入一个中间对象proxy,让timer的target引用为中间对象proxy
- 中间对象proxy的target弱引用viewcontroller
- 但是有个问题:timer触发时,调用的是proxy的方法,可是触发方法在viewcontroller怎么办呢?
- 解决办法:消息转发
- 解决办法
-
解决办法:
//中间对象proxy #import <Foundation/Foundation.h> @interface ZHProxy : NSObject +(instancetype)proxyWithTarget:(id)target; @end #import "ZHProxy.h" #import <objc/runtime.h> @interface ZHProxy () @property (nonatomic,weak) id target; @end @implementation ZHProxy +(instancetype)proxyWithTarget:(id)target{ ZHProxy *proxy = [[ZHProxy alloc] init]; proxy.target = target; return proxy; } //消息转发 -(id)forwardingTargetForSelector:(SEL)aSelector{ return self.target; } @end //使用 #import "ZHTestViewController.h" #import "ZHProxy.h" @interface ZHTestViewController () @property (nonatomic,strong) CADisplayLink *playLink; @property (nonatomic,strong) NSTimer *timer; @end @implementation ZHTestViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = UIColor.blueColor; //定时器 //每隔一段时间掉一共一次方法 //调用时间不用设置,调用时间间隔跟屏幕刷帧频率一样。 //displayLinkWithTarget: selector: 该方法会对target捕捉,造成循环引用 // self.playLink = [CADisplayLink displayLinkWithTarget:[ZHProxy proxyWithTarget:self] selector:@selector(test)]; // [self.playLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; /*********NSTimer**********/ //scheduledTimerWithTimeInterval也对target有一个强引用,导致循环引用 //这个却不能使用weakself self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[ZHProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES]; } - (void)linkTest { NSLog(@"%s",__func__); } - (void)timerTest { NSLog(@"%s",__func__); } -(void)dealloc{ // [self.playLink invalidate]; [self.timer invalidate]; } @end
-
NSProxy
-
NSProxy类
@interface NSProxy <NSObject> { Class isa; } //NSObject比较 @interface NSObject <NSObject> { Class isa; }
- NSProxy是一个跟NSObject一样的基类,不继承任何父类
- 这个类专门用于做消息转发的
- 效率高
- 不需要通过像NSObject对象一样通过isa一系列查找方法来进行了
- 当前对象没有相应的方法,直接走消息转发
- 初始化不需要
init
-
如果上面的ZHProxy继承自NSProxy,如下
#import <Foundation/Foundation.h> @interface ZHProxy1 : NSProxy +(instancetype)proxyWithTarget:(id)target; @end #import "ZHProxy1.h" @interface ZHProxy1 () @property (nonatomic,weak) id target; @end @implementation ZHProxy1 +(instancetype)proxyWithTarget:(id)target{ //NSProxy没有init方法 ZHProxy1 *proxy = [ZHProxy1 alloc]; proxy.target = target; return proxy; } //返回方法签名 -(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{ return [self.target methodSignatureForSelector:sel]; } // -(void)forwardInvocation:(NSInvocation *)invocation{ // invocation.target = self.target; [invocation invokeWithTarget:self.target]; } @end
-
GCD定时器
- NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
- 而GCD的定时器会更加准时
- 不依赖于runloop
- 直接与系统的内核挂钩
-
简单使用:
#import "ZHTestViewController.h" @interface ZHTestViewController () @property (nonatomic,strong) dispatch_source_t timer; @end @implementation ZHTestViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = UIColor.blueColor; //队列 dispatch_queue_t queue = dispatch_get_main_queue(); //自动会在子线程执行 // dispatch_queue_t queue = dispatch_queue_create("queuegcd", DISPATCH_QUEUE_SERIAL); //设置时间 NSTimeInterval start = 2.0; //2秒后开始执行 NSTimeInterval interval = 1.0;//每隔1秒执行一次 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); self.timer = timer; //设置时间 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC,0); //设置回调 dispatch_source_set_event_handler(timer, ^{ NSLog(@"============="); }); //启动定时器 dispatch_resume(timer); }
-
封装使用:
#import <Foundation/Foundation.h> @interface ZHTimer : NSObject /** 用GCD封装一个定时器 @param task 任务 @param start n秒后开始执行 @param interval 每隔多少秒执行一次 @param repeat 是否重复执行 @param async 是否是异步执行 @return 当前任务标识 */ +(NSString *)excue:(void (^)(void)) task start:(NSTimeInterval )start interval:(NSTimeInterval)interval repeats:(BOOL)repeat async:(BOOL)async; /** 取消定时任务 @param task 定时任务标识 */ +(void)cancelTask:(NSString *)task; @end #import "ZHTimer.h" @implementation ZHTimer static NSMutableDictionary *timers; static dispatch_semaphore_t seamp_; /** +initialize会被调用0次,一次,多次 调用0次:表明这个类没有被使用到 调用1次:表明只有这个类被使用到,它的子类要不然是没有被使用到,要不然就是有这个子类自己的+initialize(子类中+initialize中没有[super initialize]代码)方法 调用多次:表明这个类或者它的多个子类被使用了,并且这多个子类没有自己的+initialize方法,子类会根据OC消息发送流程而调用了它父类的+initialize方法。 */ +(void)initialize{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ timers = [NSMutableDictionary dictionary]; seamp_ = dispatch_semaphore_create(1); }); //有可能多线程同时创建定时器,那么这个全局字典就会出现线程安全隐患 // dispatch_semaphore_t seamp = dispatch_semaphore_create(1); } + (NSString *)excue:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeat async:(BOOL)async{ if (!task || (interval <0 && repeat)|| start <0 ) return nil; dispatch_queue_t queue = async ? dispatch_queue_create("queuegcd", DISPATCH_QUEUE_SERIAL) : dispatch_get_main_queue(); //设置时间 //注意这个timer要被强引用,否则就会释放 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(seamp_, DISPATCH_TIME_FOREVER); //定时器的唯一标识 NSString *name = [NSString stringWithFormat:@"%zd",timers.count]; timers[name] = timer; //解锁 dispatch_semaphore_signal(seamp_); //设置回调 dispatch_source_set_event_handler(timer, ^{ task(); if (!repeat) {//不重复任务 [self cancelTask:name]; } }); //启动定时器 dispatch_resume(timer); return name; } +(void)cancelTask:(NSString *)task{ if (!task.length) return; //加锁 dispatch_semaphore_wait(seamp_, DISPATCH_TIME_FOREVER); if (!timers[task]) return; dispatch_source_cancel(timers[task]); [timers removeObjectForKey:task]; //解锁 dispatch_semaphore_signal(seamp_); } @end //使用 #import "ZHTestViewController.h" #import "ZHTimer.h" @interface ZHTestViewController () @property (nonatomic,copy) NSString *task; @end @implementation ZHTestViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = UIColor.blueColor; NSLog(@"began-----"); NSString *task = [ZHTimer excue:^{ NSLog(@"--------%@",[NSThread currentThread]); } start:2.0 interval:1.0 repeats:NO async:YES]; self.task = task; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [ZHTimer cancelTask:self.task]; } @end