這幾天項(xiàng)目中需要用到離線請(qǐng)求,于是我就想到了利用隊(duì)列來(lái)實(shí)現(xiàn)這個(gè)功能,首先這個(gè)隊(duì)列需要的功能如下。
- 隊(duì)列能夠添加任務(wù)
- 隊(duì)列能夠被持久化到本地(從本地解析到內(nèi)存)
- 任務(wù)之間可以有依賴(lài)關(guān)系
- 任務(wù)失敗能夠自動(dòng)重試,可以設(shè)置重試次數(shù)
明確了需求以后就可以開(kāi)干了。說(shuō)到隊(duì)列,我立馬想到了FIFO的隊(duì)列,也就是先進(jìn)先出的隊(duì)列,與棧的模型剛好反過(guò)來(lái)。iOS中并沒(méi)有現(xiàn)在的隊(duì)列數(shù)據(jù)結(jié)構(gòu)可以用,但是iOS里面有一個(gè)東西叫做NSOperationQueue這貨不是一個(gè)普通的隊(duì)列,他的功能十分的強(qiáng)大,能夠處理并發(fā)的操作,是蘋(píng)果在cocoa層對(duì)多線程的一層封裝。他有一個(gè)屬性叫做maxConcurrentOperationCount,這個(gè)指定了能夠有多少個(gè)任務(wù)能夠在同一時(shí)間被執(zhí)行,如果大于1那么就是并行的隊(duì)列,如果等于1就是串行的隊(duì)列。這個(gè)模型就像一間房子,里面有很多人,maxConcurrentOperationCount就是門(mén)的數(shù)量,門(mén)越多可以同時(shí)出去的人就越多,還有就是如果人的地位越高,也就是說(shuō)任務(wù)的優(yōu)先級(jí)越高的話是可以先出去的。
那么我們就通過(guò)改造NSOperationQueue來(lái)實(shí)現(xiàn)我們自己的隊(duì)列。
首先我們繼承NSOperationQueue來(lái)新建一個(gè)類(lèi)Queue
public class Queue: NSOperationQueue {
...
}
隊(duì)列有一些基本的屬性。比如說(shuō)任務(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來(lái)新建一個(gè)QueueTask
任務(wù)也有一些一些基本的屬性,比如說(shuō)所屬隊(duì)列,任務(wù)ID,任務(wù)類(lèi)型,這里通過(guò)任務(wù)類(lèi)型來(lái)對(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ì)列里面。
這里我們來(lái)重寫(xiě)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ì)丟失,下次再打開(kāi)應(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ù)類(lèi)型來(lái)查找不同的執(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)
}
}
具體的代碼見(jiàn)Github