NSTimmer
方法一
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface IDLTimerProxy : NSObject

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

@end

NS_ASSUME_NONNULL_END
#import "IDLTimerProxy.h"

@interface IDLTimerProxy()

@property(nonatomic, assign, readwrite) SEL selector;
@property(nonatomic, weak, readwrite) id target;
@property(nonatomic, weak, readwrite) NSTimer *timer;

@end

@implementation IDLTimerProxy

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
IDLTimerProxy *proxy = [IDLTimerProxy new];
proxy.target = aTarget;
proxy.selector = aSelector;
proxy.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:proxy selector:@selector(performSelectorWithTimmer:) userInfo:userInfo repeats:yesOrNo];
return proxy.timer;
}

- (void)performSelectorWithTimmer:(NSTimer *)timmer {
if(self.target && [self.target respondsToSelector:self.selector]) {
[self.target performSelector:self.selector withObject:timmer.userInfo];
} else {
[self.timer invalidate];
self.timer = nil;
}
}
@end

调用方式:

self.timmer = [IDLTimerProxy scheduledTimerWithTimeInterval:5 target:self selector:@selector(run:) userInfo:nil repeats:YES];
方法二
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface IDLWeakTimerProxy : NSProxy

+ (instancetype)proxyWithTarget:(id)targe;

@end

NS_ASSUME_NONNULL_END
#import "IDLWeakTimerProxy.h"

@interface IDLWeakTimerProxy ()

@property(nonatomic, strong, readwrite) id target;

@end

@implementation IDLWeakTimerProxy

+ (instancetype)proxyWithTarget:(id)target {
return [[IDLWeakTimerProxy alloc] initWithTarget:target];
}

- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
if(!invocation) return;
SEL selector = invocation.selector;
if([self.target respondsToSelector:selector]) {
[invocation invokeWithTarget:self.target];
}
}

- (BOOL)respondsToSelector:(SEL)aSelector {
return [self.target respondsToSelector:aSelector];
}

@end

调用方式:

self.weakTimmerProxy = [IDLWeakTimerProxy proxyWithTarget:self];
self.timmer = [NSTimer scheduledTimerWithTimeInterval:3 target:self.weakTimmerProxy selector:@selector(run:) userInfo:nil repeats:YES];
方法三
@implementation NSTimer (IDLBlockTimer)

+ (NSTimer *)idl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats {

return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(idl_blockSelector:) userInfo:[block copy] repeats:repeats];
}

+ (void)idl_blockSelector:(NSTimer *)timer {

void(^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
dispatch_source_t
self.gcdTimmer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(self.gcdTimmer, dispatch_time(DISPATCH_TIME_NOW, 0), 3 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.gcdTimmer, ^{

});
dispatch_resume(self.gcdTimmer);
  • CADisplayLink 与 NSTimer 的区别:
  • CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容绘制到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息,CADisplayLink类对应的selector就会被调用一次。

  • NSTimer以指定的模式注册到runloop后,每当设定的周期时间到达后,runloop会向指定的target发送一次指定的selector消息。

  • 对于精度而言

由于iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下精确度相当高。NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在忙于别的调用,触发时间就会推迟到下一个runloop周期。NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间范围。

  • 对于使用场景而言

CADisplayLink使用场合相对专一,适合做界面的不停重绘。而NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。

  • 例子:

    self.weakTimmerProxy = [IDLWeakTimerProxy proxyWithTarget:self];
    self.cadisplaylink = [CADisplayLink displayLinkWithTarget:self.weakTimmerProxy selector:@selector(run:)];
    [self.cadisplaylink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    self.cadisplaylink.paused = YES;
  • CADisplayLink 接口:

@interface CADisplayLink : NSObject
{
@private
void *_impl;
}

//每秒多少帧
@property(nonatomic) NSInteger preferredFramesPerSecond
API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0));

// 创建 display link
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;

//将display link 添加到 runloop 除非停止,否则在每个vsync信号到来的时候定时调用selector
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;

//将display link 从runloop中移除
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;

// 将display link 从所有的runloop modes中移除,并且释放target 对象。
- (void)invalidate;

// 暂停display link
@property(getter=isPaused, nonatomic) BOOL paused;

@property(readonly, nonatomic) CFTimeInterval timestamp; //表示屏幕显示的上一帧的时间戳
@property(readonly, nonatomic) CFTimeInterval duration; //两帧间隔时间 默认为16.6ms
@property(readonly, nonatomic) CFTimeInterval targetTimestamp
API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0));
@end
Contents
  1. 1. NSTimmer
  2. 2. 方法一
  3. 3. 方法二
  4. 4. 方法三
  5. 5. dispatch_source_t
  6. 6. CADisplayLink