NSTimer
NSTimer是iOS开发当中最常用的定时器。其底层是通过Runloop来实现的,大部分情况下比较准确。但是当前循环耗时操作较多时,会出现延迟问题。同时,也受所加入的RunLoop的Mode影响。
创建之后手动添加到哪个线程的RunLoop中,就运行在哪个线程。
1 2 3 4 5 6 7
| + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
注意:在即RunLoop的UITrackingMode下,定时器会失效。解决办法即将定时器加到RunLoop的commonModes上即可。
在哪个线程创建就会被自动加入到哪个线程的RunLoop中,就运行在哪个线程。
1 2 3 4 5
| + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
|
NSTimer定时器设置了延时之后,如果需要让它立刻执行,需要使用fire方法
NSTimer定时器的释放一定要先将其终止,而后才能销毁对象
1 2
| [timer invalidate]; timer = nil;
|
对于NSTimer,如何解除循环引用,有一种方式特地介绍一下。
NSProxy是除了NSObject之外的另一个基类,是一个抽象类,只能继承它,重写其消息转发的方法,将消息转发给另一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @interface WeakProxy : NSProxy
- (instancetype)initWithTarget:(id)target;
@end
@interface WeakProxy ()
@property (nonatomic, weak) id target;
@end
@implementation WeakProxy
- (instancetype)initWithTarget:(id)target { self = [WeakProxy alloc]; self.target = target; return self; }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; }
- (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; }
@end
|
除了重载消息转发机制的两个方法之外,NSProxy也没有其他功能了。即,使用NSProxy注定是用来转发消息的。
NSProxy可以用来模拟多继承,proxy对象处理多个不同Class对象的消息。
继承自NSProxy的代理类会自动转发消息,而继承自NSObject的则不会,需要自行根据消息转发机制来进行处理。
NSObject的Category中的方法不能转发。
CADisplayLink
CADisplayLink是基于屏幕刷新的周期,所以其一般很准时,默认每秒刷新60次。其本质也是通过RunLoop,所以不难看出,当RunLoop选择其他模式或耗时操作过多时,仍旧会造成延迟。
1 2 3 4 5 6 7 8 9
| + (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
- (void)invalidate;
@property(nonatomic) NSInteger preferredFramesPerSecond;
|
CADisplayLink是以屏幕刷新频率将内容绘制到屏幕上的定时器,适合做UI的不停重绘,动画或视频的渲染等。
一旦CADisplayLink以特定的模式添加到RunLoop中,每当屏幕需要刷新的时候,RunLoop就会调用CADisplayLink绑定的target上的selector方法,则target就可获取CADisplayLink的每次调用的时间戳,用于准备下一帧显示的数据。可用于动画或视频。使用CADisplayLink同样要注意循环引用的问题。
在日常开发中,适当使用CADisplayLink甚至有优化作用。比如对于需要动态计算进度的进度条,由于进度反馈主要是为了UI更新,那么当计算进度的频率超过帧数时,就造成了很多无谓的计算。如果将计算进度的方法绑定到CADisplayLink上来调用,则只在每次屏幕刷新时计算进度,优化了性能。MBProcessHUD则是利用了这一特性。
GCDTimer
GCD定时器实际上是使用了dispatch源(dispatch source),dispatch源监听系统内核对象并处理,通过系统级调用,更加精准。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @implementation viewController { dispatch_source_t timer; }
- (void)viewDidLoad { [super viewDidLoad]; // 创建定时器对象 gcd可以指定队列:即可以在主队列也可以在子队列上执行任务 timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0)); // 参数: 1 定时器 2 任务开始时间 3任务的间隔 4可接受的误差时间,设置0即不允许出现误差 dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0.0*NSEC_PER_SEC);
// 设置定时器任务 dispatch_source_set_event_handler(timer, ^{ });
// 启动任务,GCD计时器创建后需要手动启动 dispatch_resume(timer); }
- (void)invalidate { // 暂停 dispatch_suspend(timer); // 销毁 dispatch_cancel(timer); timer = nil; }
@end
|
GCD Timer的封装
1、用一个字典存储定时器,在取消的时候,根据定时器的key找到相应的定时器
2、多线程会造成字典不安全,对字典读写操作的时候需要加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| @interface GCDTimer : 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
@implementation GCDTimer
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
|