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
常用的应该是前俩种,timerWithTimeInterval
和initWithFireDate
需要手动加入到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];
|
解决方式三
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]; }
|
解决方式四
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
|
CADisplayLink
- 保证调用频率和屏幕的刷帧频率一致,60FPS
- 在动画中最好用CADisplayLink,因为由于每秒的刷新频率较高,所以用它所生成的动画会显得非常流畅。
- 创建方式和使用跟
NSTimer
大同小异,在此不做探究
GCDTimer
NSTimer
依赖于runloop
,如果runloop
任务过于繁重,可能造成NSTimer
不准时
使用起来也很简单:
- 创建一个定时器队列
dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL);
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