Swift NSOperation と NSOperationQueue の基本的な使い方
NSOperationQueueクラスは dispatch_queue_t の高レベルAPIで、NSOpertionオブジェクトの実行を規制する。 NSOperationクラスは継承して使用することを目的とした抽象基底クラスになっているので、通常はNSOperationを継承したクラスを作成する。FoundationフレームワークにはNSOperationクラスを継承した NSInvocationOperation と NSBlockOperation クラスの2つが用意されている。
NSOperationQueueにNSOperationオブジェクトを追加する場合、NSOperationQueueが実行スレッドを作成するので、結果として非並行オペレーション(nonconcurrent operation)は非同期で実行される。(今回は並行オペレーション(concurrent operation)については説明しない。)
カスタム非並行オペレーションの実装
非並行オペレーションの場合、NSOperationのサブクラスで実装する内容は以下の2点
- メインタスクの実装 (main メソッドをオーバーライド)
- キャンセルされた時の実装
0~9を出力する例
class CountingOperation: NSOperation { override func main() { if !self.cancelled { // 0~9を出力 for i in 0..<10 { print(i) } } else { // キャンセル状態の処理 print("Cancelled") } } }
オペレーションの実行
NSOperationQueueを使用してオペレーションを実行するには、Queueオブジェクトに addOperation でオペレーションを追加すれば、オペレーションの状態がreadyになったものからプライオリティに従って実行される。
let queue = NSOperationQueue() // Queueの生成 for _ in 0..<100 { let operation = CountingOperation() queue.addOperation(operation) // 0~9が非同期で出力される }
※ Queueの生成を NSOperationQueue.mainQueue() とすることでメインスレッドに関連するキューを取得する。この時のオペレーションはメインスレッドで同期的に実行される。 queue.maxConcurrentOperationCount = 1 とした場合も、メインスレッド以外でオペレーションは同期的に実行される。
オペレーションのコンプリーションハンドラの設定
オペレーションには、タスクが終了・キャンセルしたときに呼ばれる引数も戻り値も取らないコンプリーションハンドラを設定できる。ほとんどの場合、コンプリーションハンドラはオペレーションが実行されているスレッドとは別のスレッドで実行される(例えば、メインスレッドで実行されているオペレンションのコンプリーションハンドラがメインスレッドで実行されるとは限らない)。
operation.completionBlock = { print("complete") }
オペレーション間の依存関係
オペレーションには依存関係を設定することが可能。
下の例では、オペレーションAがオペレーションBとCに依存しているので、
オペレーションBとCの終了を待って、終了したら、オペレーションAが実行される。
let opA = NSBlockOperation(block: {print("A")}) let opB = NSBlockOperation(block: {for i in 0..<10 {print("B:", i)}}) let opC = NSBlockOperation(block: {for i in 10..<20 {print("C:", i)}}) opA.addDependency(opB) // opAがopBに依存 opA.addDependency(opC) // opAがopCに依存 let queue = NSOperationQueue() queue.addOperation(opA) queue.addOperation(opB) queue.addOperation(opC) // 最後にAが出力される
NSOperationQueueの終了検知
NSOperationQueueの終了を検知するにはoperationCountをキー値監視(KVO)する。
class SomeOperations: NSObject { var queue: NSOperationQueue! func startOperations() { queue = NSOperationQueue() // operationCountを監視する queue.addObserver(self, forKeyPath: "operationCount", options: .New, context: nil) let operation = NSBlockOperation(block: {for i in 0..<10 {print(i)}}) queue.addOperation(operation) } // KVO override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if keyPath == "operationCount" { let count = change!["new"] as! Int // 簡単のためForced unwrapping // Queueに追加したオペレーションが全て終了 if count == 0 { print("Queue Complete") } } } }
NSOperationQueue の Quality of Service (QoS)
NSOperationQueueにQoSを設定することができる。 デフォルのQoSはmainQueueから生成されたキューの場合は UserInteractive、それ以外は、Background。