Swift3のGCD周りのまとめ


Swift3でGrand Central Dispatchも大幅に変わっているので、簡単にまとめました。

dispatch_queue

concurrent queue(実行スレッドは複数で同時に複数タスク)やserial queue(実行スレッドは1つでタスクごとに違うスレッドで実行される可能性はあるが、同時に1タスク)の生成は、以下のように行います。

// concurrent queue
let concurrentQueue = DispatchQueue(label: "com.example.concurrent-queue", attributes: .concurrent)
// serial queue
let serialQueue = DispatchQueue(label: "com.example.serial-queue")

dispatch_get_global_queue

global queueはDispatchQoS.QoSClassの優先度をもとに、下記のように取得することができます。

// 上から順に優先度が高いもの
enum QoSClass {
    case userInteractive
    case userInitiated
    case default
    case utility
    case background
    case unspecified
}
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)

dispatch_get_main_queue

main queueは下記のように取得することができます。

let mainQueue = DispatchQueue.main

dispatch_async

非同期で処理を実行する場合は、下記のようになります。

let queue = DispatchQueue.global()
queue.async {
    print("dispatch_async")
}

dispatch_sync

同期で処理を実行する場合は、下記のようになります。

let queue = DispatchQueue.global()
queue.sync {
    print("dispatch_sync")
}

dispatch_after

Swift3ではインターバルを生成するために下記のenumが用意されています。

enum DispatchTimeInterval {
    case seconds(Int)
    case milliseconds(Int)
    case microseconds(Int)
    case nanoseconds(Int)
}

処理を指定した時間が経過してから実行する場合は、下記のようになります。

let dispatchTime: DispatchTime = DispatchTime.now() + DispatchTimeInterval.seconds(1)
DispatchQueue.global().asyncAfter(deadline: dispatchTime) {
    print("dispatchTime = \(dispatchTime)")
}

dispatch_barrier_async

下記のコードのように0から99までループをさせて、10で割り切れる数字のみ文字列を置き換えて、それ以外の場合はprintするという処理を行います。

let queue = DispatchQueue(label: "com.sample.barrier", attributes: .concurrent)
var string = ""
for i in 0..<99 {
    guard i % 10 == 0 else {
        queue.async {
            print("\(i) : string = " + string)
        }
        continue
    }
    queue.async {
        let range = string.startIndex..<string.index(string.startIndex, offsetBy: string.characters.count)
        string.removeSubrange(range)
        string += "\(i)"
    }
}

並列に非同期で処理しているので、removeSubrange完了時にprintが呼ばれている可能性があり、空文字列の状態がprintされてしまっているときがあります。

// 実行結果
4 : string =
7 : string = 0
3 : string =
1 : string =
6 : string =
2 : string =
5 : string = 0
8 : string = 0
9 : string = 0

下記のようにasync(flags: .barrier)を使うことで、文字列操作が実行される際のタスクが1つになります。

let queue = DispatchQueue(label: "com.sample.barrier", attributes: .concurrent)
var string = ""
for i in 0..<99 {
    guard i % 10 == 0 else {
        queue.async {
            print("\(i) : string = " + string)
        }
        continue
    }
    queue.async(flags: .barrier) {
        let range = string.startIndex..<string.index(string.startIndex, offsetBy: string.characters.count)
        string.removeSubrange(range)
        string += "\(i)"
    }
}
// 実行結果
6 : string = 0
7 : string = 0
8 : string = 0
9 : string = 0
11 : string = 10
13 : string = 10
12 : string = 10
15 : string = 10
14 : string = 10

dispatch_group

asyncを使ったり、waitを使う場合は、下記のようになります。

let group = DispatchGroup()
for i in 0..<100 {
    DispatchQueue.global().async(group: group) {
        print(i)
    }
}

_ = group.wait(timeout: .distantFuture)

print("after wait")
// 実行結果
1
2
// 中略
98
99
after wait

enterleaveを使ったり、nofityを使う場合は、下記のようになります。

let group = DispatchGroup()
for i in 0..<100 {
    group.enter()
    DispatchQueue.global().async {
        print(i)
        group.leave()
    }
}

group.notify(queue: .global()) {
    print("notify closure called")
}

print("after notify")
// 実行結果
1
2
// 中略
98
99
after notify
notify closure called

dispatch_once

Swift3ではdispatch_onceに相当するものがなくなりました。
シングルトンのインスタンスを一度だけ初期化するなどの処理は

class Hoge {
    static let shared = Hoge()
}

のようにstaticなPropertyをletで定義することで実現できます。

一方、一度だけ実行したい処理がある場合は

class ViewController: UIViewController {

    lazy var __once: Void = {
        self.printSomething()
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        _ = __once // Void型のPropertyの初期化が一度だけ実行されるので、`self.printSomething()`が呼ばれる
        _ = __once // 既にPropertyの初期化が完了しているので、`self.printSomething()`が呼ばれない
    }

    func printSomething() {
        print("this method might be executed once.")
    }
}

のように、Void型のPropertyをlazyで初期化を行い、その中で処理を実行することで実現できます。

上記の方法は

The free function dispatch_once is no longer available in Swift. In Swift, you can use lazily initialized globals or static properties and get the same thread-safety and called-once guarantees as dispatch_once provided. Example:

let myGlobal = { … global contains initialization in a call to a closure … }()
_ = myGlobal  // using myGlobal will invoke the initialization code only 

のようにMigrating to Swift 2.3 or Swift 3 from Swift 2.2に記載されています。