GCDTimer

NSTimer

相关参数介绍

1.interval: 时间间隔,单位秒,如果设置小于0,系统默认是0.1
2.target: 定时器的绑定对象,一般是当前控制器self
3.selector: 需要执行的实例方法(减号方法)
4.userInfo: 传递相关信息
5.repeats: 是否循环
6.block: 需要执行的代码块,等同于 selector方法
7.invocation: 需要执行的方法
8.fireDate: 触发时间

创建方式
  • timerWithTimeInterval
  • scheduledTimerWithTimeInterval
  • initWithFireDate

常用的应该是前俩种,timerWithTimeIntervalinitWithFireDate需要手动加入到runloop中,而scheduledTimerWithTimeInterval会将定时器自定加入到当前的runloop,即NSDefaultRunLoopMode

问题

由于定时器会对使用对象(target)强引用,当界面对定时器强持有的时候,这时候会造成循环引用,比较常见的问题就是在即将要释放的ViewController不走它的dealloc方法,所以在dealloc方法中[self.timer invalidate]不会生效

解决方式一
  • 合适的位置销毁timer,[self.timer invalidate]
    解决方式二
  • 利用block的特性,__weak打破循环
1
2
3
4
5
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf test];
}];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
解决方式三
  • 利用一个中间对象 MidObject
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
@interface MidObject : NSObject
+ (instancetype)doSomethingWithTarget: (id)target;
@property (nonatomic, weak) id target;
@end
@implementation MidObject
+ (instancetype)doSomethingWithTarget:(id)target{
MidObject *obj = [[MidObject alloc] init];
obj.target = target;
return obj;
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
return self.target;
}
@end
#import "ViewController.h"
#import "MidObject.h"
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[MidObject doSomethingWithTarget:self] selector:@selector(test) userInfo:nil repeats:YES];
}
- (void)test{
NSLog(@"%s",__func__);
}
- (void)dealloc{
NSLog(@"%s",__func__);
[self.timer invalidate];
}
解决方式四
  • 利用专门做消息转发的NSProxy,效率更高
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
@interface TimerProxy : NSProxy
+ (instancetype)proxyWithTarget: (id)target;
@property (nonatomic, weak) id target;
@end
@implementation TimerProxy
+ (instancetype)proxyWithTarget:(id)target{
TimerProxy *proxy = [TimerProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
@end
#import "ViewController.h"
#import "TimerProxy.h"
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TimerProxy proxyWithTarget:self] selector:@selector(test) userInfo:nil repeats:YES];
}
- (void)test{
NSLog(@"%s",__func__);
}
- (void)dealloc{
NSLog(@"%s",__func__);
[self.timer invalidate];
}
@end
  • 保证调用频率和屏幕的刷帧频率一致,60FPS
  • 在动画中最好用CADisplayLink,因为由于每秒的刷新频率较高,所以用它所生成的动画会显得非常流畅。
  • 创建方式和使用跟NSTimer大同小异,在此不做探究

GCDTimer

NSTimer依赖于runloop,如果runloop任务过于繁重,可能造成NSTimer不准时

使用起来也很简单:

  • 创建一个定时器队列
    dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL);
  • 创建一个定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

  • 设置时间

    • source 定时器
    • start 开始时间,设置dispatch_timeDISPATCH_TIME_NOW,使用系统默认时钟计时,当系统休眠,定时器会停止;设置dispatch_walltime按照真实时间来计时
    • interval 间隔(DISPATCH_TIME_FOREVER 执行一次)
    • leeway 误差,一般设置为0
dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway)

  uint64_t start = 2.0; // 2秒后开始执行
  uint64_t interval = 1.0; // 每隔1秒执行
  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, ^{ });

  • 启动定时器

dispatch_resume(timer);

Xcode已经有现成的代码块了,敲 dispatch_source就可以找到,不会造成内存泄漏的问题

基于封装的思想,将GCDTimer封装下
GCDTimer

这个人很帅<br>他什么都不想说<br>