iOS的使用Swift,NSOperation实现的倒计时

前段时间,公司项目有个需求要求实现任务倒计时,头疼死我了,折腾了我老半天,先看最终的实现效果,如下图。当时的需求有两点要求:

  1. 要求在当前任务关卡实现倒计时计算;
  2. 要求在弹出来的tips页面同时也进行倒计时。

timeCountDown.gif

一想到倒计时,我们可能想到的解决方案有三种;NStimer、GCD、NSOperation

NSTimer实现倒计时

NSTimer实现计时需要注意,他默认是在runloop中的NSDefaultRunLoopMode计时,在这个模式下面,有滑动事件,计时将失效,此时我们需要在将timer添加到runloop中的NSRunLoopCommonModes,这样就不会有任何影响

1
2
3
let animationTimer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: #selector(WeeklyMissionViewController.runanimation), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(animationTimer!, forMode: NSRunLoopCommonModes)
animationTimer!.fire()

GCD实现倒计时

GCD实现计时需要注意的是let _timer: dispatch_source_t必须存储为全局变量timer = _timer

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
private func setGCDTimer(weeklyMission: MissionList, type: Int) {
// 计算倒计时
let nowDate = NSDate()
let nowUnix = nowDate.timeIntervalSince1970

let count = (weeklyMission.createdAt)! + 24 * 3600 - Int(nowUnix)
var _timeout: Int = count
let _queue: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let _timer: dispatch_source_t = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _queue)
timer = _timer
// 每秒执行
dispatch_source_set_timer(_timer, dispatch_walltime(nil, 0), 1 * NSEC_PER_SEC, 0)

printLog("----_timer-----")
dispatch_source_set_event_handler(_timer) { () -> Void in

if _timeout <= 0 {
// 倒计时结束
dispatch_source_cancel(_timer)
dispatch_async(dispatch_get_main_queue(), { [unowned self] () -> Void in
// 如需更新UI 代码请写在这里
})
} else {

print("cell:\(weeklyMission.mission)---\(_timeout)")
_timeout -= 1
let hours = _timeout / 3600
let hoursSec = hours * 3600
let minutes = (_timeout - hoursSec) / 60
let seconds = _timeout - hoursSec - minutes * 60

dispatch_async(dispatch_get_main_queue(), { [unowned self] in
let timeText = "\(String(format: "%.2d",hours)):\(String(format: "%.2d",minutes)):\(String(format: "%.2d",seconds))"
// 如需更新UI 代码请写在这里
})
}
}
dispatch_resume(_timer)
}

NSOperation实现倒计时

以上两种实现的计时,有个很明显的缺点就是,不可控!他们二者开启一个计时器之后,没法方便的控制他停止,继续;但是NSOperation不同,他有cancel方法,我们可以拿到对应的operation,然后操作他,可控性好。
主要代码如下:

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//
// TimeCountDownManager.swift
// leapParent
//
// Created by romance on 16/9/19.
// Copyright © 2016年 Firstleap. All rights reserved.
//

import UIKit

/// 计时中回调
typealias TimeCountingDownTaskBlock = (timeInterval: NSTimeInterval) -> Void
// 计时结束后回调
typealias TimeFinishedBlock = (timeInterval: NSTimeInterval) -> Void
private var shareInstance = TimeCountDownManager()

final class TimeCountDownManager: NSObject {
// 单利
class var sharedInstance : TimeCountDownManager {
return shareInstance
}

var pool: NSOperationQueue

override init() {
pool = NSOperationQueue()
super.init()
}

/**
* 开始倒计时,如果倒计时管理器里具有相同的key,则直接开始回调。
*
* @param Key 任务key,用于标示唯一性
* @param timeInterval 倒计时总时间,
* @param countingDown 倒计时时,会多次回调,提供当前秒数
* @param finished 倒计时结束时调用,提供当前秒数,值恒为 0
*/
func scheduledCountDownWith(key: String, timeInteval: NSTimeInterval, countingDown:TimeCountingDownTaskBlock?,finished:TimeCountingDownTaskBlock?) {

var task: TimeCountDownTask?
if coundownTaskExistWith(key, task: &task) {
task?.countingDownBlcok = countingDown
task?.finishedBlcok = finished
if countingDown != nil {
countingDown!(timeInterval: (task?.leftTimeInterval) ?? 60)
}
} else {
task = TimeCountDownTask()
task?.leftTimeInterval = timeInteval
task?.countingDownBlcok = countingDown
task?.finishedBlcok = finished
task?.name = key

pool.addOperation(task!)
}
}

/**
* 查询倒计时任务是否存在
*
* @param akey 任务key
* @param task 任务
* @return YES - 存在, NO - 不存在
*/
func coundownTaskExistWith(key: String,inout task: TimeCountDownTask? ) -> Bool {
var taskExits = false

for (_, obj) in pool.operations.enumerate() {
let temptask = obj as! TimeCountDownTask
if temptask.name == key {
task = temptask
taskExits = true
// print("coundownTaskExistWith#####\(temptask.leftTimeInterval)")
break
}
}
return taskExits
}

/**
* 取消所有倒计时任务
*/
func cancelAllTask() {
pool.cancelAllOperations()
}

/**
* 挂起所有倒计时任务
*/
private func suspendAllTask() {
pool.suspended = true
}
}


final class TimeCountDownTask: NSOperation {
var leftTimeInterval: NSTimeInterval = 0
var countingDownBlcok: TimeCountingDownTaskBlock?
var finishedBlcok: TimeFinishedBlock?

override func main() {

if self.cancelled {
return
}
while leftTimeInterval > 0 {
print("leftTimeInterval----\(leftTimeInterval)")

if self.cancelled {
return
}
leftTimeInterval -= 1
dispatch_async(dispatch_get_main_queue(), {
if self.countingDownBlcok != nil {
self.countingDownBlcok!(timeInterval: self.leftTimeInterval)
}
})
NSThread.sleepForTimeInterval(1)
}

dispatch_async(dispatch_get_main_queue()) {
if self.cancelled {
return
}
if self.finishedBlcok != nil {
self.finishedBlcok!(timeInterval: 0)
}
}
}
}

稍微解析下以上代码,TimeCountDownManager是定时器管理类,是个单利,可以管理app中所有需要倒计时的task,TimeCountDownTask是具体的用来处理倒计时的NSOperation子类,大家还可以在我的基础上进行完善,比如cancel具体taskIdentifier的task,suspended具体的task,等等!

整个demo代码的GitHub地址