【Swift】非同期処理「Operation」について

#Operation、OperationQueueクラス

非同期処理に関して、以前GCDの記事を投稿しましたが、
GCDはコアライブラリのlibdispatchで実装されています。

同じくコアライブラリのFoundationにも非同期処理を実現するクラスである、
`Operation`クラスと`OperationQueue`クラスがあります。

Operationクラスは、実行されるタスクとその情報をカプセル化したものです。

**このOperationクラスのインスタンスがキューに入れられて順次実行されます。
この時にキューの役割を果たすのがOperationQueueクラスになります。**

##実行方法
###タスクの定義

GCDの時はタスクはクロージャ内に書いていましたが、
Operationを使う時は、Operationクラスのサブクラスとして定義します。

クラスとして表すことによって少し手間ですがメリットもあり、
扱いやすいインターフェースを提供することができます。

なお、処理内容はmain( )メソッドの中に記述します。

```swift

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class SomeOperation: Operation {
    let value: Int
    init(value: Int) {
        self.value = value
    }
    
    override func main() {
        // スレッドを１秒止める
        Thread.sleep(forTimeInterval: 1)
        print(value)
    }
}
```

これでタスクを定義することができたので、
次にこのタスクをキューに入れる必要があります。

ですが、キューに入れる前にキューを作らなければいけません。

###キューの生成
タスクを実行するキューとなる**OperationQueueクラス**のインスタンスを作成します。

OperationQueueクラスは引数なしのイニシャライザでインスタンス化できます。
インスタンス化後にプロパティ経由で各種設定を行います。

```swift

import Fountation

let queue = OperationQueue()
```

**・nameプロパティ**
nameプロパティを設定するとキューに名前をつけることができます。
この値には**逆順DNS形式**を使用することが一般的です。

**・maxConcurrentOperationCountプロパティ**
特定のキューが同時に実行できるタスク数はシステムの状況から適切に判断されますが、
maxConcurrentOperationCountプロパティを使うことでその数を指定することができます。

当たり前ですが、大きな数を設定しても高速になる訳ではなく、
むしろ遅くなる可能性がありますのでご注意ください。

**・QualityOfService型**
GCDにはQoSで実行優先度を決めることができましたが、
それのOperationQueueクラスバージョンがこれになります。

QualityOfService型の値の役割はGCDのQoSと同様です。

```swift

import Foundation

let queue = OperationQueue()
queue.name = "com.example.my_operation_queue"
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .userInitiated
```

キューとタスクは作ることができたので、
キューにタスクを追加する必要があります。

###キューへのタスクの追加

タスクの追加方法は、OperationQueueクラスの`addOperation(_:)`メソッドを使用します。
引数にはOperationクラスの値を渡します。

また、OperationQueueクラスには複数のOperationクラスを渡すためのメソッドである、
`addOperations(_:waitUntilFinished:)`も用意されています。

第一引数の型は[Operation]型になります。

次のサンプルコードでは、
SampleOperation型の値を５個作成しキューに追加しています。

第二引数の`waitUntilFinished`にtrueを与えると、
**全てのタスクの実行が終わるまで呼び出したスレッドをブロックします。**

今回はfalseを与えているので、タスクの終了を待たずにそのまま次の処理を実行します。

```swift

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class SampleOperation: Operation {
    let value: Int
    init(value: Int) {
        self.value = value
    }
    
    override func main() {
        Thread.sleep(forTimeInterval: 1)
        print("value: \(value)")
    }
}

let queue = OperationQueue()
queue.name = "com.example.my_operation_queue"
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .userInitiated

var operations: [SampleOperation] = []

for i in 0..<5 {
    operations.append(SampleOperation(value: i))
}

queue.addOperations(operations, waitUntilFinished: false)
print("OperationQueueにタスクを追加しました。")

実行結果
OperationQueueにタスクを追加しました。
value: 0
value: 1
value: 2
value: 3
value: 4
```

`maxConcurrentOperationCount = 2`のように指定しているため、
１秒おきにタスクを２つずつ実行していきます。

`Thread.sleep(forTimeInterval: 1)`をコメントアウトした場合は
下記のような実行結果になります。

```swift

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class SampleOperation: Operation {
    let value: Int
    init(value: Int) {
        self.value = value
    }
    
    override func main() {
//        Thread.sleep(forTimeInterval: 1)
        print("value: \(value)")
    }
}

let queue = OperationQueue()
queue.name = "com.example.my_operation_queue"
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .userInitiated

var operations: [SampleOperation] = []

for i in 0..<5 {
    operations.append(SampleOperation(value: i))
}

queue.addOperations(operations, waitUntilFinished: false)
print("OperationQueueにタスクを追加しました。")

実行結果
value: 0
value: 1
OperationQueueにタスクを追加しました。
value: 2
value: 3
value: 4
```

###タスクのキャンセル

Operationクラスにはタスクをキャンセルする機能を持つ`cancel()`メソッドがあります。

次のサンプルコードでは、先ほどのコードにcansel( )メソッドを追加しています。
`operations[2].cancel()`により value: ２が存在しないのがわかります。

```swift

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class SampleOperation: Operation {
    let value: Int
    init(value: Int) {
        self.value = value
    }
    
    override func main() {
        print("value: \(value)")
    }
}

let queue = OperationQueue()
queue.name = "com.example.my_operation_queue"
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .userInitiated

var operations: [SampleOperation] = []

for i in 0..<5 {
    operations.append(SampleOperation(value: i))
}

queue.addOperations(operations, waitUntilFinished: false)
operations[2].cancel()

実行結果
value: 0
value: 1
value: 3
value: 4
```
###タスクの依存関係の設定

**Operationクラスでは複数のタスク間での依存関係を設定することができます。**

依存関係を定義することにより、
あるタスクが終了してからでないとこのタスクを実行しないよ！といった処理ができます。

あるタスクに対して、それよりも先に実行されるべきタスクを指定するには、
Operationクラスの`addDependency(_:)`メソッドを使用します。

引数には先に実行されるべきタスクを入れます。


```swift

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class FirstOperation: Operation {
    override func main() {
        print("This is FirstOperation")
    }
}

class SecendOperation: Operation {
    override func main() {
        print("This is SecondOperation")
    }
}


let queue = OperationQueue()
queue.name = "com.example.my_operation_queue"
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .userInteractive

var operations: [Operation] = []

operations.append(FirstOperation())
operations.append(SecendOperation())

operations[0].addDependency(operations[1])
queue.addOperations(operations, waitUntilFinished: false)

実行結果
This is SecondOperation
This is FirstOperation
```

FirstOperation( )が先に実行されるはずでしたが、
`operations[0].addDependency(operations[1])`の部分で
依存関係を定義しているのでSecondOperation( )から実行されました。

##使うタイミング

###複数の非同期処理を実装する
Operation、OperationQueueクラスは、
非同期処理をオブジェクト指向で抽象化したものです。

単純なスレッドの切り替えの他に、
タスクの依存関係やキャンセル機能などが備わっているため、
GCDよりも**複雑な処理に向いています。**

また、Operation、OperationQueueクラスは、内部でGCDを利用しているので
**GCDで定義できることはOperation、OperationQueueクラスでも定義することができます。**

GCDはタスクをクロージャで表すことができ、
キューを生成しなくてもグローバルキューを利用できます。

Operation、OperationQueueクラスはタスクの定義やキューの生成が必要ですが、
その分複雑な処理を行うことができます。

**したがって、単純な非同期処理はGCDで定義し、
キャンセルや依存関係の定義はOperation、OperationQueueクラスで定義しましょう！**

他にも非同期処理にはThreadクラスがありますが、
**使う機会はほぼないらしいので紹介はしません。**

もし私が使う機会があったらその時に共有できたらなと思います。

以上、最後までご覧いただきありがとうございました。