这几天项目中需要用到离线请求,于是我就想到了利用队列来实现这个功能,首先这个队列需要的功能如下。
- 队列能够添加任务
- 队列能够被持久化到本地(从本地解析到内存)
- 任务之间可以有依赖关系
- 任务失败能够自动重试,可以设置重试次数
明确了需求以后就可以开干了。说到队列,我立马想到了FIFO的队列,也就是先进先出的队列,与栈的模型刚好反过来。iOS中并没有现在的队列数据结构可以用,但是iOS里面有一个东西叫做NSOperationQueue
这货不是一个普通的队列,他的功能十分的强大,能够处理并发的操作,是苹果在cocoa层对多线程的一层封装。他有一个属性叫做maxConcurrentOperationCount
,这个指定了能够有多少个任务能够在同一时间被执行,如果大于1那么就是并行的队列,如果等于1就是串行的队列。这个模型就像一间房子,里面有很多人,maxConcurrentOperationCount
就是门的数量,门越多可以同时出去的人就越多,还有就是如果人的地位越高,也就是说任务的优先级越高的话是可以先出去的。
那么我们就通过改造NSOperationQueue
来实现我们自己的队列。
首先我们继承NSOperationQueue
来新建一个类Queue
public class Queue: NSOperationQueue {
...
}
队列有一些基本的属性。比如说任务的名字,最大并发数量,最大重试次数等。
/// the max times of retries when the task failing
public let maxRetries: Int//最大重试次数
var taskCallbacks = [String: TaskCallBack]()存放任务执行方法的数组
var taskList = [String: QueueTask]()队列任务数组
let serializationProvider: QueueSerializationProvider?//序列化助手
let logProvider: QueueLogProvider?//log助手
public required init(queueName: String, maxConcurrency: Int = 1,
maxRetries: Int = 5,
serializationProvider: QueueSerializationProvider? = nil,
logProvider: QueueLogProvider? = nil) {
self.maxRetries = maxRetries
self.serializationProvider = serializationProvider
self.logProvider = logProvider
super.init()
self.name = queueName
self.maxConcurrentOperationCount = maxConcurrency
}
然后继承NSOperation
来新建一个QueueTask
任务也有一些一些基本的属性,比如说所属队列,任务ID,任务类型,这里通过任务类型来对任务进行分组,让同一组的任务采用相同的处理方法。
public class QueueTask: NSOperation {
public let queue: Queue
public var taskID: String
public var taskType: String
public var retries: Int
public let created: NSDate
public var started: NSDate?
public var userInfo: AnyObject?
}
现在最基本的样子已经有了,现在有了任务和队列,那么下一步就需要把任务添加到队列里面。
这里我们来重写NSOperationQueue
的addOperation
方法,
/**
add Queue task to the queue and it will automaticly invoke the start method
- parameter op: Queuetask
*/
public override func addOperation(op: NSOperation) {
if let task = op as? QueueTask {
taskList[task.taskID] = task
print(taskList)
}
super.addOperation(op)
}
这里将任务添加到了NSOperationQueue
同时也加到了taskList
里面,任务完成之后会从里面移除,这里这样做主要是讲任务直接持久化到本地,只有在任务真正完成之后才会被移除,这样即使中间退出了应用任务也不会丢失,下次再打开应用的时候会自动的从本地把让任务加载到队列之中。这个在处理一些离线网络请求的场景的时候很有帮助。在离线情况下对本地数据进行操作,相应的网络请求会暂存在队列之中。等待网络恢复之后再进行请求。
这里注意到addOperation
方法执行之后任务会直接进去pending状态,相当于自动调用了Operation
的start方法。
这里可以看到Queue
中有这么一个方法,根据任务类型来查找不同的执行方法。
func runTask(task: QueueTask) {
if let callback = taskCallbacks[task.taskType] {
callback(task)
} else {
print("no callback registerd for task")
}
}
完成之后会从队列中被移除。
func taskComplete(op: NSOperation) {
if let task = op as? QueueTask {
taskList.removeValueForKey(task.taskID)
}
}
具体的代码见Github