LoginSignup
11
8

More than 3 years have passed since last update.

Operation、OperationQueueクラス

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

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

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

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

実行方法

タスクの定義

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

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

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


import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class SomeOperation: Operation {
    let value: Int
    init(value: Int) {
        self.value = value
    }

    override func main() {
        // スレッドを1秒止める
        Thread.sleep(forTimeInterval: 1)
        print(value)
    }
}

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

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

キューの生成

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

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


import Fountation

let queue = OperationQueue()

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

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

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

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

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


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型の値を5個作成しキューに追加しています。

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

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


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のように指定しているため、
1秒おきにタスクを2つずつ実行していきます。

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


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: 2が存在しないのがわかります。


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(_:)メソッドを使用します。

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


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クラスがありますが、
使う機会はほぼないらしいので紹介はしません。

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

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

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8