@@ -12,22 +12,22 @@ tags:
12
12
---
13
13
14
14
15
- # iOS定时器
16
-
17
15
# 前言
18
16
19
- 定时器的使用是软件开发基础技能,用于延时执行或重复执行某些方法。大部分人接触iOS的定时器都是从
17
+ 定时器的使用是软件开发基础技能,用于延时执行或重复执行某些方法。
18
+
19
+ 我相信大部分人接触iOS的定时器都是从这段代码开始的:
20
20
21
21
``` objc
22
22
[NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector:@selector (action:) userInfo: nil repeats: YES ]
23
23
```
24
24
25
- 这段代码开始的吧。
26
-
27
- 但是关于iOS定时器,你真的会用吗?
25
+ 但是你真的会用吗?
28
26
29
27
# 正文
30
28
29
+ ## iOS定时器
30
+
31
31
首先来介绍iOS中的定时器
32
32
33
33
iOS中的定时器大致分为这几类:
@@ -39,6 +39,7 @@ iOS中的定时器大致分为这几类:
39
39
### NSTimer
40
40
41
41
#### 使用方法
42
+
42
43
**NSTime**定时器是我们比较常使用的定时器,比较常使用的方法有两种:
43
44
44
45
```objc
@@ -48,7 +49,7 @@ iOS中的定时器大致分为这几类:
48
49
```
49
50
这两种方法都是创建一个定时器,区别是用` timerWithTimeInterval: ` 方法创建的定时器需要手动加入RunLoop中。
50
51
51
- ``` objc
52
+ ```
52
53
// 创建NSTimer对象
53
54
NSTimer *timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
54
55
// 加入RunLoop中
@@ -59,7 +60,7 @@ NSTimer *timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector
59
60
60
61
举个例子:
61
62
62
- ```objc
63
+ ```
63
64
- (void)startTimer{
64
65
NSTimer *UIScrollView = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(action:) userInfo:nil repeats:YES];
65
66
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
@@ -76,7 +77,7 @@ NSTimer *timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector
76
77
77
78
打印台输出:
78
79
79
- ![ ] ( http://ww1.sinaimg.cn/large/006tNc79gw1farbzzwcevj30ci04ljtm .jpg )
80
+ ![ ] ( http://upload-images.jianshu.io/upload_images/2178672-9de097ecc618b498 .jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 )
80
81
81
82
可以看出在滑动` UIScrollView ` 时,定时器被暂停了。
82
83
@@ -85,22 +86,22 @@ NSTimer *timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector
85
86
86
87
1 . ** timer** 分别添加到 ` UITrackingRunLoopMode ` 和 ` NSDefaultRunLoopMode ` 中
87
88
88
- ```objc
89
- [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
90
- [[NSRunLoop mainRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];
91
- ```
89
+ ``` objc
90
+ [[NSRunLoop mainRunLoop ] addTimer: timer forMode: NSDefaultRunLoopMode ] ;
91
+ [[ NSRunLoop mainRunLoop] addTimer: timer forMode: UITrackingRunLoopMode] ;
92
+ ```
92
93
93
94
2. 直接将**timer**添加到`NSRunLoopCommonModes` 中:
94
95
95
- ```objc
96
- [[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];
97
- ```
96
+ ```objc
97
+ [[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];
98
+ ```
98
99
99
100
但并不是都** timer** 所有的需要在滑动` UIScrollView ` 时继续执行,比如使用** NSTimer** 完成的帧动画,滑动` UIScrollView ` 时就可以停止帧动画,保证滑动的流程性。
100
101
101
- 若没有特殊要求的话,一般使用第二种方法创建完 ** timer** ,会自动添加到` NSDefaultRunLoopMode ` 中去执行,也是平时最常用的方法。
102
+ 若没有特殊要求的话,一般使用第二种方法创建完** timer** ,会自动添加到` NSDefaultRunLoopMode ` 中去执行,也是平时最常用的方法。
102
103
103
- ``` objc
104
+ ```
104
105
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:YES];
105
106
```
106
107
参数:
@@ -121,7 +122,7 @@ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selecto
121
122
122
123
释放方法:
123
124
124
- ```objc
125
+ ```
125
126
// 停止定时器
126
127
[timer invalidate];
127
128
```
@@ -130,13 +131,13 @@ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selecto
130
131
131
132
** iOS10.0** 推出了两个新的API,与上面的方法相比,` selector ` 换成Block回调以、减少传入的参数(那几个参数真是鸡肋)。不过开发中一般需要适配低版本,还是尽量使用上面的方法吧。
132
133
133
- ``` objc
134
+ ```
134
135
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
135
136
136
137
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
137
138
```
138
139
139
- #### 特点
140
+ ###特点
140
141
141
142
- ** 必须加入Runloop**
142
143
@@ -150,23 +151,23 @@ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selecto
150
151
- ** UIScrollView滑动会暂停计时**
151
152
152
153
添加到`NSDefaultRunLoopMode`的 `timer` 在 `UIScrollView`滑动时会暂停,若不想被`UIScrollView`滑动影响,需要将 `timer` 添加再到 `UITrackingRunLoopMode` 或 直接添加到`NSRunLoopCommonModes` 中
153
-
154
154
155
-
156
- CADisplayLink
157
- ---
155
+
156
+
157
+ ##CADisplayLink
158
+
158
159
159
160
CADisplayLink官方介绍:
160
161
> A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display
161
162
162
163
** CADisplayLink** 对象是一个和屏幕刷新率同步的定时器对象。每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的` target ` 发送一次指定的` selector ` 消息, CADisplayLink类对应的 ` selector ` 就会被调用一次。
163
164
164
165
从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染,或者做动画。
165
- #### 使用方法
166
+ ###使用方法
166
167
167
168
创建:
168
169
169
- ```objc
170
+ ```
170
171
@property (nonatomic, strong) CADisplayLink *displayLink;
171
172
172
173
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
@@ -178,7 +179,7 @@ self.displayLink.frameInterval = 1;
178
179
```
179
180
释放方法:
180
181
181
- ``` objc
182
+ ```
182
183
[self.displayLink invalidate];
183
184
184
185
self.displayLink = nil;
@@ -196,7 +197,7 @@ self.displayLink = nil;
196
197
`CFTimeInterval`值为`readOnly`,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在`targe`t的`selector`被首次调用以后才会被赋值。`selector`的调用间隔时间计算方式是:**调用间隔时间 = duration × frameInterval**。
197
198
198
199
199
- #### 特点
200
+ ###特点
200
201
201
202
- ** 刷新频率固定**
202
203
@@ -209,56 +210,28 @@ self.displayLink = nil;
209
210
210
211
CADisplayLink可以确保系统渲染每一帧的时候我们的方法都被调用,从而保证了动画的流畅性。
211
212
212
- GCD定时器
213
- ---
213
+ ## GCD定时器
214
+
214
215
** GCD定时器** 和NSTimer是不一样的,NSTimer受RunLoop影响,但是GCD的定时器不受影响,因为通过源码可知RunLoop也是基于GCD的实现的,所以GCD定时器有非常高的精度。关于GCD的使用可一看看[ 这篇博客] ( http://www.cnblogs.com/pure/archive/2013/03/31/2977420.html ) 。
215
216
216
- #### 使用方法
217
+ ###使用方法
217
218
创建GCD定时器定时器的方法稍微比较复杂,看下面的代码:
218
219
219
- ``` objc
220
- // 定时时间
221
- int interval = 1 ;
222
-
223
- // 创建全局队列
224
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
225
-
226
- // 创建定时器
227
- dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0 , 0 , queue);
228
-
229
- // 设置定时器 interval * NSEC_PER_SEC 定时间隔几秒
230
- dispatch_source_set_timer (timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
231
-
232
- dispatch_source_set_event_handler(timer, ^{
233
-
234
- dispatch_async(dispatch_get_main_queue(), ^{
235
-
236
- // 需要执行的代码
237
- });
238
- });
239
-
240
- // 开启定时器
241
- dispatch_resume(timer);
242
-
243
- // 关闭定时器
244
- dispatch_source_cancel(timer);
245
- ```
246
-
247
- #### 单次的延时调用
220
+ ####单次的延时调用
248
221
NSObject中的` performSelector:withObject:afterDelay: ` 以及 ` performSelector:withObject:afterDelay:inModes: ` 这两个方法在调用的时候会设置当前 runloop 中 ` timer ` ,前者设置的 ` timer ` 在 ` NSDefaultRunLoopMode ` 运行,后者则可以指定 ** NSRunLoop** 的 ` mode ` 来执行。我们上面介绍过 runloop 中 ` timer ` 在 ` UITrackingRunLoopMode ` 被挂起,就导致了代码就会一直等待 ` timer ` 的调度,解决办法在上面也有说明。
249
222
250
223
不过我们可以用另一套方案来解决这个问题,就是使用GCD中的 ` dispatch_after ` 来实现单次的延时调用:
251
224
252
- ```objc
225
+ ```
253
226
double delayInSeconds = 2.0;
254
227
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
255
228
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
256
229
[self someMethod];
257
230
});
258
231
```
259
232
260
- #### 循环调用
261
- ``` objc
233
+ ####循环调用
234
+ ```
262
235
// 创建GCD定时器
263
236
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
264
237
@@ -300,14 +273,14 @@ dispatch_source_cancel(_timer);
300
273
- 创建的` timer ` 一定要有` dispatch_suspend(_timer) ` 或` dispatch_source_cancel(_timer) ` 这两句话来指定出口,否则定时器将不执行,若我们想无限循环可将 ` dispatch_source_cancel(_timer) ` 写在一句永不执行的` if ` 判断语句中。
301
274
302
275
303
- 使用场景
304
- ---
276
+ ## 使用场景
277
+
305
278
介绍完iOS中的各种定时器,接下来我们来说说这几种定时器在开发中的几种用法。
306
279
###短信重发倒计时
307
280
308
281
短信倒计时使我们登录注册常用的功能,一般设置为60s,实现方法如下:
309
282
310
- ```objc
283
+ ```
311
284
// 计时时间
312
285
@property (nonatomic, assign) int timeout;
313
286
@@ -377,12 +350,12 @@ dispatch_source_cancel(_timer);
377
350
378
351
效果如下:
379
352
380
- ![ ] ( http://ww3.sinaimg.cn/large/006tNc79jw1faspxkoemhj30ci0m8mxy .jpg )
353
+ ![ ] ( http://upload-images.jianshu.io/upload_images/2178672-3d4d1353bcc36026 .jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 )
381
354
382
- [ 代码的gitHub链接 ] ( https://github.com/qiubaiying/BYTimer )
355
+ ##### [ 代码链接 ] ( https://github.com/qiubaiying/BYTimer )
383
356
384
357
385
- ### 每个几分钟向服务器发送数据
358
+ ###每个几分钟向服务器发送数据
386
359
387
360
在有定位服务的APP中,我们需要每个一段时间将定位数据发送到服务器,比如每5s定位一次每隔5分钟将再统一将数据发送服务器,这样会处理比较省电。
388
361
一般程序进入后台时,定时器会停止,但是在定位APP中,需要持续进行定位,APP在后台时依旧可以运行,所以在后台定时器也是可以运行的。
@@ -393,7 +366,7 @@ dispatch_source_cancel(_timer);
393
366
394
367
这里我们使用** NSTimer** 来创建一个每个5分钟执行一次的定时器.
395
368
396
- ``` objc
369
+ ```
397
370
#import <Foundation/Foundation.h>
398
371
399
372
typedef void(^TimerBlock)();
@@ -408,7 +381,7 @@ typedef void(^TimerBlock)();
408
381
409
382
```
410
383
411
- ```objc
384
+ ```
412
385
#import "BYTimer.h"
413
386
414
387
@interface BYTimer ()
@@ -440,12 +413,18 @@ typedef void(^TimerBlock)();
440
413
}
441
414
442
415
@end
443
-
444
416
```
445
417
446
- 该接口的实现很简单,就是** NSTimer** 创建了一个300s执行一次的定时器,但是要注意定时器需要加入` NSRunLoopCommonModes ` 中。
418
+ 该接口的实现很简单,就是 ** NSTimer** 创建了一个300s执行一次的定时器,但是要注意定时器需要加入` NSRunLoopCommonModes ` 中。
419
+
420
+ 要使定时器在后台能运行,app 就需要在 [ 后台常驻] ( http://waitingyuan.blog.163.com/blog/static/2155781652014111133150534/ ) 。
421
+
422
+ # 结语
447
423
448
- 最后,要使定时器在后台能运行,app就需要在 [ 后台常驻 ] ( http://waitingyuan.blog.163.com/blog/static/2155781652014111133150534/ ) 。
424
+ 最后总结一下:
449
425
426
+ NSTimer 使用简单方便,但是应用条件有限。
450
427
428
+ CADisplayLink 刷新频率与屏幕帧数相同,用于绘制动画。具体使用可看我封装好的一个 [ 水波纹动画] ( https://github.com/qiubaiying/WaterRippleView ) 。
451
429
430
+ GCD定时器 精度高,可控性强,使用稍复杂。
0 commit comments