這幾天項(xiàng)目中需要用到離線請(qǐng)求管行,于是我就想到了利用隊(duì)列來實(shí)現(xiàn)這個(gè)功能,首先這個(gè)隊(duì)列需要的功能如下。
- 隊(duì)列能夠添加任務(wù)
- 隊(duì)列能夠被持久化到本地(從本地解析到內(nèi)存)
- 任務(wù)之間可以有依賴關(guān)系
- 任務(wù)失敗能夠自動(dòng)重試,可以設(shè)置重試次數(shù)
明確了需求以后就可以開干了梢卸。說到隊(duì)列,我立馬想到了FIFO的隊(duì)列副女,也就是先進(jìn)先出的隊(duì)列蛤高,與棧的模型剛好反過來。iOS中并沒有現(xiàn)在的隊(duì)列數(shù)據(jù)結(jié)構(gòu)可以用,但是iOS里面有一個(gè)東西叫做NSOperationQueue
這貨不是一個(gè)普通的隊(duì)列襟齿,他的功能十分的強(qiáng)大姻锁,能夠處理并發(fā)的操作枕赵,是蘋果在cocoa層對(duì)多線程的一層封裝猜欺。他有一個(gè)屬性叫做maxConcurrentOperationCount
,這個(gè)指定了能夠有多少個(gè)任務(wù)能夠在同一時(shí)間被執(zhí)行拷窜,如果大于1那么就是并行的隊(duì)列开皿,如果等于1就是串行的隊(duì)列。這個(gè)模型就像一間房子篮昧,里面有很多人赋荆,maxConcurrentOperationCount
就是門的數(shù)量,門越多可以同時(shí)出去的人就越多懊昨,還有就是如果人的地位越高窄潭,也就是說任務(wù)的優(yōu)先級(jí)越高的話是可以先出去的。
那么我們就通過改造NSOperationQueue
來實(shí)現(xiàn)我們自己的隊(duì)列酵颁。
首先我們繼承NSOperationQueue
來新建一個(gè)類Queue
public class Queue: NSOperationQueue {
...
}
隊(duì)列有一些基本的屬性嫉你。比如說任務(wù)的名字,最大并發(fā)數(shù)量躏惋,最大重試次數(shù)等幽污。
/// the max times of retries when the task failing
public let maxRetries: Int//最大重試次數(shù)
var taskCallbacks = [String: TaskCallBack]()存放任務(wù)執(zhí)行方法的數(shù)組
var taskList = [String: QueueTask]()隊(duì)列任務(wù)數(shù)組
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
來新建一個(gè)QueueTask
任務(wù)也有一些一些基本的屬性,比如說所屬隊(duì)列簿姨,任務(wù)ID距误,任務(wù)類型,這里通過任務(wù)類型來對(duì)任務(wù)進(jìn)行分組扁位,讓同一組的任務(wù)采用相同的處理方法准潭。
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?
}
現(xiàn)在最基本的樣子已經(jīng)有了,現(xiàn)在有了任務(wù)和隊(duì)列域仇,那么下一步就需要把任務(wù)添加到隊(duì)列里面刑然。
這里我們來重寫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)
}
這里將任務(wù)添加到了NSOperationQueue
同時(shí)也加到了taskList
里面殉簸,任務(wù)完成之后會(huì)從里面移除闰集,這里這樣做主要是講任務(wù)直接持久化到本地,只有在任務(wù)真正完成之后才會(huì)被移除般卑,這樣即使中間退出了應(yīng)用任務(wù)也不會(huì)丟失武鲁,下次再打開應(yīng)用的時(shí)候會(huì)自動(dòng)的從本地把讓任務(wù)加載到隊(duì)列之中。這個(gè)在處理一些離線網(wǎng)絡(luò)請(qǐng)求的場(chǎng)景的時(shí)候很有幫助蝠检。在離線情況下對(duì)本地?cái)?shù)據(jù)進(jìn)行操作沐鼠,相應(yīng)的網(wǎng)絡(luò)請(qǐng)求會(huì)暫存在隊(duì)列之中。等待網(wǎng)絡(luò)恢復(fù)之后再進(jìn)行請(qǐng)求。
這里注意到addOperation
方法執(zhí)行之后任務(wù)會(huì)直接進(jìn)去pending狀態(tài)饲梭,相當(dāng)于自動(dòng)調(diào)用了Operation
的start方法乘盖。
這里可以看到Queue
中有這么一個(gè)方法,根據(jù)任務(wù)類型來查找不同的執(zhí)行方法憔涉。
func runTask(task: QueueTask) {
if let callback = taskCallbacks[task.taskType] {
callback(task)
} else {
print("no callback registerd for task")
}
}
完成之后會(huì)從隊(duì)列中被移除订框。
func taskComplete(op: NSOperation) {
if let task = op as? QueueTask {
taskList.removeValueForKey(task.taskID)
}
}
具體的代碼見Github