Operation(NSOperation)について

もっと簡潔にこちらでまとめ直しています。
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には以下の２種類存在し、実装するタスクが同期的か非同期的かで、どちらを実装するかを決める
  * 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の実装例.swift
      var _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()

```non_concurrent_operation_sample.swift
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になったときである
  
```concurrent_operation_sample.swift
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でも無視されていると思われる
  * 以下[リファレンス](https://developer.apple.com/documentation/foundation/operation/1408275-isasynchronous)の説明
	  * オペレーションがそのタスクを非同期的に実行するかどうかを指定する
		  * falseの場合 = queueに追加すると同時に呼び出し元のスレッド上でオペレーションが実行される
		  * trueの場合 = オペレーションは別スレッド上で実行される
	  * デフォルトの値はtrueである
	  * macOS10.6以降はこの値は無視される
  * しかしながらiOSでもこの値は参照されていないため無視されていると思われる
  * operationを呼び出し元関数と同期したい場合は以下のメソッドを呼び出す 
 
    ```Operation.swift
    func waiteUntileFinished()
    ```
  
	  * 上記の関数をqueueに追加した直後でよびだす
	  * queueから削除されるまでコードが止まるので、mainスレッド上では呼び出さないこと

* オペレーションを直列(逐次)実行させたい場合は、queue側に実装されている `maxConcurrentOperationCount` プロパティの値を1にする


# 参考
* [[Qiita] Foundation.Operationの並列オペレーションがよくわからない人向けの説明](https://qiita.com/yimajo/items/7a0f46cd33d07bbdb04d)
* [[Qiita] [Swift]Operationを使って複数のNCMBリクエストを実行し、メインスレッドに戻す方法](https://qiita.com/oonoyosp/items/2c8337afedfa48f0e89b)
* [Understanding Operation and OperationQueue in Swift](http://agostini.tech/2017/07/30/understanding-operation-and-operationqueue-in-swift/)