開(kāi)發(fā)中經(jīng)常遇到異步任務(wù)之間有依賴關(guān)系坯认,需要對(duì)執(zhí)行順序進(jìn)行調(diào)度的情況。
比如氓涣,一個(gè)頁(yè)面要組合多個(gè)后端接口的數(shù)據(jù)牛哺,必須所有請(qǐng)求都完成后,再進(jìn)行數(shù)據(jù)組裝劳吠,最后刷新UI引润。
如果是同步任務(wù),解決方案很簡(jiǎn)單痒玩〈靖剑可以用dispatch_group
,更方便的是用NSOperation
的addDependency
功能蠢古,讓最后執(zhí)行的任務(wù)依賴前面幾個(gè)任務(wù)即可奴曙。
我們知道,NSOperation
內(nèi)部維護(hù)了一個(gè)狀態(tài)機(jī)來(lái)表示內(nèi)部任務(wù)的執(zhí)行狀態(tài)草讶。一共有下面幾個(gè)狀態(tài):
- ready
- executing
- finished
- cancelled
如果我們用addDependency
給兩個(gè)NSOperation
設(shè)置了依賴關(guān)系缆毁,那么一個(gè)NSOperation
對(duì)應(yīng)的方法或block執(zhí)行完畢后,會(huì)變?yōu)閒inished狀態(tài)到涂,這時(shí)另一個(gè)NSOperation
才會(huì)執(zhí)行脊框。
如果我們能讓異步任務(wù)表現(xiàn)得像同步任務(wù)一樣,在異步任務(wù)收到回調(diào)后才變?yōu)?code>finished狀態(tài)践啄,不就可以用addDependency
來(lái)控制異步任務(wù)的執(zhí)行順序了嗎浇雹?
事實(shí)上蘋(píng)果在NSOperation
的接口里給我們留了一個(gè)口子∮旆恚看NSOperation
的接口:
NS_CLASS_AVAILABLE(10_5, 2_0)
@interface NSOperation : NSObject {
@private
id _private;
int32_t _private1;
#if __LP64__
int32_t _private1b;
#endif
}
- (void)start;
- (void)main;
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
@property (readonly, getter=isReady) BOOL ready;
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
@property NSOperationQueuePriority queuePriority;
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
@end
我們需要重點(diǎn)關(guān)注的是asynchronous
屬性昭灵。如果它值為true
,那么這個(gè)NSOperation
執(zhí)行完畢后不會(huì)自動(dòng)變?yōu)閒inished狀態(tài)伐谈,需要手動(dòng)設(shè)置烂完。這正是我們想要的。
我們可以寫(xiě)一個(gè)NSOperation
的子類诵棵,給異步任務(wù)提供一個(gè)設(shè)為finished狀態(tài)的接口抠蚣。
上代碼:
typealias MLAsyncOperationBlock = (operation:MLAsyncOperation)->Void
class MLAsyncOperation: NSOperation {
private var ml_executing = false{
willSet {
willChangeValueForKey("isExecuting")
}
didSet {
didChangeValueForKey("isExecuting")
}
}
private var ml_finished = false{
willSet {
willChangeValueForKey("isFinished")
}
didSet {
didChangeValueForKey("isFinished")
}
}
private var block:MLAsyncOperationBlock?
override var asynchronous:Bool {
return true
}
override var concurrent:Bool {
return true
}
override var finished:Bool{
return ml_finished
}
override var executing:Bool{
return ml_executing
}
convenience init(operationBlock:MLAsyncOperationBlock) {
self.init()
block = operationBlock
}
override func start() {
if cancelled {
ml_finished = true
return
}
ml_executing = true
block?(operation: self)
}
func finishOperation(){
ml_executing = false
ml_finished = true
}
deinit{
print("operation deinited")
}
}
幾個(gè)需要說(shuō)明的點(diǎn):
一:
NSOperation
內(nèi)部有一組成員變量來(lái)維護(hù)它的executing、finished這些狀態(tài)履澳,我們?cè)L問(wèn)不到嘶窄。但我們可以另外加一組成員變量怀跛,自己來(lái)維護(hù)這些狀態(tài)。一個(gè)子類不一定要訪問(wèn)父類的成員變量,只要接口表現(xiàn)得和父類一樣就行了。
二:
NSOperationQueue
是通過(guò)KVO觀察內(nèi)部的NSOperation
狀態(tài)的變化巡社,來(lái)自動(dòng)管理NSOperation
的執(zhí)行的。我們?cè)谠O(shè)置自己的ml_executing
屬性的時(shí)候漓拾,需要表現(xiàn)得像executing
屬性被設(shè)置了一樣。也就是需要調(diào)用一下willChangeValueForKey("isExecuting")
和didChangeValueForKey("isExecuting")
兩個(gè)方法戒祠。利用Swift屬性的willSet和didSet特性晦攒,可以非常方便地實(shí)現(xiàn)。
finished屬性同理得哆。
三:
finishOperation
這個(gè)方法,是用戶在收到異步回調(diào)哟旗,任務(wù)完成后需要調(diào)用的贩据。調(diào)用后這個(gè)operation就會(huì)變?yōu)閒inished狀態(tài)。為了方便闸餐,我給MLAsyncOperationBlock
加了一個(gè)MLAsyncOperation
類型的參數(shù)饱亮,在調(diào)用內(nèi)部block的時(shí)候會(huì)把self
傳進(jìn)去。這樣用戶在構(gòu)造任務(wù)block的時(shí)候舍沙,通過(guò)這個(gè)參數(shù)就直接可以訪問(wèn)到operation本身了近上。
來(lái)簡(jiǎn)單測(cè)試一下好不好用。
先寫(xiě)一個(gè)類實(shí)現(xiàn)一個(gè)同步方法和兩個(gè)異步方法:
class TestClass {
let queue = dispatch_queue_create("TestClass_background_queue", DISPATCH_QUEUE_CONCURRENT)
func method1(){
print("method 1 begin")
for _ in 0 ... 100000 {
continue
}
print("method 1 end")
}
func asyncMethod1(done:()->Void){
print("async method 1 begin")
dispatch_async(queue) { () -> Void in
for _ in 0 ... 100000 {
continue
}
print("async method 1 end")
done()
}
return
}
func asyncMethod2(done:()->Void){
print("async method 2 begin")
dispatch_async(queue) { () -> Void in
for _ in 0 ... 100000 {
continue
}
print("async method 2 end")
done()
}
return
}
}
測(cè)試代碼:
let operationQueue = NSOperationQueue()
operationQueue.maxConcurrentOperationCount = 5
let object = TestClass()
let op1 = NSBlockOperation {
object.method1()
}
let asyncOp1 = MLAsyncOperation { (operation) in
object.asyncMethod1{ () -> Void in
operation.finishOperation()
}
}
let asyncOp2 = MLAsyncOperation { (operation) in
object.asyncMethod2{
operation.finishOperation()
}
}
op1.addDependency(asyncOp1)
op1.addDependency(asyncOp2)
operationQueue.addOperation(asyncOp1)
operationQueue.addOperation(asyncOp2)
operationQueue.addOperation(op1)
let runloop = NSRunLoop.currentRunLoop()
while runloop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture()){
continue
}
由于測(cè)試工程是一個(gè)command line tool拂铡,我用runloop阻塞住了主線程壹无,避免主線程執(zhí)行完之后整個(gè)程序退出,operationQueue
中的代碼來(lái)不及執(zhí)行感帅。
跑一下斗锭,輸出結(jié)果如下:
async method 1 begin
async method 2 begin
async method 1 end
async method 2 end
method 1 begin
method 1 end
可以看到,兩個(gè)異步方法并行執(zhí)行失球,在兩個(gè)方法都收到回調(diào)完成之后岖是,最后一個(gè)方法開(kāi)始執(zhí)行。完美實(shí)現(xiàn)了我們的需求实苞。
完整的代碼可以從我的Github下載豺撑。