もっと簡潔にこちらでまとめ直しています。
https://qiita.com/ysn551/items/2a5afc78006d2b22cce0
概要
Operation(NSOperation)についてまとめたものです。
Operationは、concurrentタイプとnon-concurrentタイプに別れますが、non-concrentオペレーションは並列で実行されないう意味ではありません。どちらのタイプのオペレーションも並列で処理されます。
non-concurrentタイプは、同期的な処理(画像の生成などの重たい処理)をオペレーション化したいときに実装します。concurrentタイプは、非同期的な処理(ネットワークからのデータの取得など)をオペレーション化したい時に実装します。
なので並列(concurrent)と非同期(asynchronous)の意味がこんがらがり、この命名は最悪だと思います。
検証環境
端末: iPhone8 plus simulator
iOS: 11
swif: 4.0.3
まとめ
-
Operationには以下の2種類存在し、実装するタスクが同期的か非同期的かで、どちらを実装するかを決める
- non-concurrentオペレーション = 同期タスクを非同期かつオペレーション化する
- concurrentオペレーション = 非同期タスクをオペレーション化する
-
non-concurrentオペレーション
- 同期的なタスクを非同期化したい場合はこちらを実装する
- mainメソッドのみオーバーライドする
- mainメソッド終了後、OperationQueueから削除される
従ってmainメソッド内で非同期的な実装が出来ないため、非同期的なタスクをオペレーション化したい場合は、concurrentタイプを実装する
-
concurrentオペレーション
-
非同期的なタスクをオペレーション化したい場合はこちらを実装する
-
startメソッドをオーバーライドする
-
startメソッドが終了してもqueueから削除されない
-
queueからの削除は
isFinished
プロパティの値がtrue
になったとき (KVOで通知する) -
以下の状態をプログラマが管理し、値の変更はKVO通知する必要がある
- isExecuting = 非同期タスク実行前にtrueにする
- isFinished = タスク完了時にtrueにする
-
isExecuting, isFinishedプロパティの実装例
setterを実装することでKVOを便利に行えるisFinishedの実装例.swiftvar _finished: Bool = false override var isFinished: Bool { get { return _finished } set { //値が変更された場合はKVO通知する self.willChangeValue(for: \SubOperation.isFinished) _finished = newValue self.didChangeValue(for: \SubOperation.isFinished) } }
-
サンプルコード
non-concurrentタイプ
- 以下のメソッドをオーバーライドする必要がある
- main()
class RequestOperation: Operation {
func main() {
//同期的なタスクを実装すること
self.createImage()
//main関数終了後自動的にqueueから削除される
}
}
concurrentタイプ
-
以下のメソッドをオーバーライドする必要がある
- start()
- isExecuting { get }
- isFinished { get }
- isAsynchronous { get } // デフォルトでtrue、しかし現状iOSでも無視されている
-
以下の値の変更は、KVO通知する必要がある
- isExecuting = 非同期処理開始前にtrueにする
- isFinished = 非同期処理完了後にtrueにする
-
queueからの削除は
isFinished
がtrueになったときである
class RequestOperation: Operation {
var object: Object? = nil
enum State {
case ready
case executing
case finished
}
private var state = State.ready {
didSet {
switch self.state {
case .ready: break
case .executing:
self.isExecuting = true
case .finished:
self.isExecuting = false
self.isFinished = true
}
}
}
private var _executing: Bool = false
override var isExecuting: Bool {
get {
return _executing
}
set {
if _executing != newValue {
self.willChangeValue(for: \RequestOperation.isExecuting)
_executing = newValue
self.didChangeValue(for: \RequestOperation.isExecuting)
}
}
}
private var _finished: Bool = false;
override var isFinished: Bool {
get {
return _finished
}
set {
if _finished != newValue {
self.willChangeValue(for: \RequestOperation.isFinished)
_finished = newValue
self.didChangeValue(for: \RequestOperation.isFinished)
}
}
}
init(object: Object?) {
super.init()
self.object = object
}
override func start() {
guard self.isCancelled == false else {
// Queueから削除される
self.state = .finshed
return
}
self.state = .executing
// データストアへの登録を実施
object?.saveInBackground({ (error) in
if error != nil {
// 保存に失敗した場合の処理
} else {
// 保存に成功した場合の処理
}
// Queueから削除される
self.state = .finished
})
// startメソッドが終了してもQueueから削除されない
}
}
Tips
-
isCancelledプロパティの評価について
startメソッド実行時など、キャンセルされたかどうかは常に頭にいれて実装する。
キャンセルされている場合は、状態をfinishedに変更して終了させることが望ましい -
isAsynchronousプロパティはiOSでも無視されていると思われる
-
以下リファレンスの説明
- オペレーションがそのタスクを非同期的に実行するかどうかを指定する
- falseの場合 = queueに追加すると同時に呼び出し元のスレッド上でオペレーションが実行される
- trueの場合 = オペレーションは別スレッド上で実行される
- デフォルトの値はtrueである
- macOS10.6以降はこの値は無視される
- オペレーションがそのタスクを非同期的に実行するかどうかを指定する
-
しかしながらiOSでもこの値は参照されていないため無視されていると思われる
-
operationを呼び出し元関数と同期したい場合は以下のメソッドを呼び出す
Operation.swiftfunc waiteUntileFinished()
- 上記の関数をqueueに追加した直後でよびだす
- queueから削除されるまでコードが止まるので、mainスレッド上では呼び出さないこと
-
-
オペレーションを直列(逐次)実行させたい場合は、queue側に実装されている
maxConcurrentOperationCount
プロパティの値を1にする