LoginSignup
161

More than 5 years have passed since last update.

Swift3のGCD周りのまとめ

Last updated at Posted at 2016-10-27

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に記載されています。

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
161