LoginSignup
9
9

More than 5 years have passed since last update.

dispatch いちいち書くの面倒だったら…自分で改良すればいいじゃない!

Posted at

まあ皆さんも多分思ってるじゃないでしょうか、バックグラウンドスレッドに切り替えたり逆にメインスレッドを呼び出したりすることがよくありますが、dispatch 系、もしくはは GCD 系の文法ってなんでいちいちそんなに書くの面倒でしょう、と。

まあ GCD 系の命令は純粋な C 言語の物なので、名前空間はもちろん、クラスなんざも当然ないし、ただの関数だから引数ラベルなんかもあるわけない。そのせいで関数名は全て dispatch_ なんちゃらになるから、今の Xcode 7.3 より古い Xcode なら補完効かせるのもすごいしんどかったです。7.3 からは途中入力も効くようになったからだいぶ助かりましたが。

それでも dispatch 系の関数を利用するのに、いちいち dispatch 系の変数を作らなきゃいけなかったりと利用するのに一手間かかるものです…

そうだ!GCD struct を作ろう

というわけで、作ってみた結果がこれです:

GCD.swift
import Foundation

struct GCD {

    enum Thread {

        case Main
        case Static(queue: dispatch_queue_t)
        case Dynamic(name: String, attribute: QueueAttribute)

        var queue: dispatch_queue_t {
            switch self {
            case .Main:
                return dispatch_get_main_queue()

            case .Static(queue: let queue):
                return queue

            case .Dynamic(name: let name, attribute: let attribute):
                return dispatch_queue_create(name, attribute.queueAttribute)
            }
        }

        enum QueueAttribute {
            case Serial
            case Concurrent

            var queueAttribute: dispatch_queue_attr_t {
                switch self {
                case .Serial:
                    return DISPATCH_QUEUE_SERIAL

                case .Concurrent:
                    return DISPATCH_QUEUE_CONCURRENT
                }
            }
        }

    }

    private init() {

    }

}

extension GCD { // MARK: Semaphores

    enum DispatchTime {
        case Forever
        case TimeAfter(delta: NSTimeInterval)

        var dispatchTimeType: dispatch_time_t {
            switch self {
            case .Forever:
                return DISPATCH_TIME_FOREVER

            case .TimeAfter(delta: let delta):
                let nanoseconds = Int64(delta * NSTimeInterval(NSEC_PER_SEC))
                let time = dispatch_time(DISPATCH_TIME_NOW, nanoseconds)
                return time
            }
        }
    }

    static func createSemaphore(value: Int) -> dispatch_semaphore_t {
        return dispatch_semaphore_create(value)
    }

    static func fireSemaphore(semaphore: dispatch_semaphore_t) {
        dispatch_semaphore_signal(semaphore)
    }

    static func waitForSemaphore(semaphore: dispatch_semaphore_t, until deadLine: DispatchTime = .Forever) {
        dispatch_semaphore_wait(semaphore, deadLine.dispatchTimeType)
    }

}

extension GCD { // MARK: Queues

    static func runSynchronizedQueue(at thread: Thread = .Main, with action: (() -> Void)) {

        dispatch_sync(thread.queue) {
            action()
        }

    }

    static func runAsynchronizedQueue(at thread: Thread = .Main, waitUntilStartForMax waitTime: NSTimeInterval? = nil, with action: (() -> Void)) {

        if let waitTime = waitTime {
            let semaphore = GCD.createSemaphore(thread.queue.hash)
            dispatch_async(thread.queue, {
                GCD.fireSemaphore(semaphore)
                action()
            })
            GCD.waitForSemaphore(semaphore, until: waitTime == 0 || waitTime == .infinity ? .Forever : .TimeAfter(delta: waitTime))

        } else {
            dispatch_async(thread.queue) {
                action()
            }
        }

    }

}

とりあえず一番よく使う dispatch_(a)sync の関数と dispatch_semaphore 系の関数を GCD という struct の静的メソッドに変身させました。これで結構使いやすくなります。例えば非同期でメインスレッドで view.setNeedsDisplay() を実行させたい、そしてメインスレッドが実際動くまで最大 1 秒待機させておきたい場合、今までですと

let semaphore = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_main_queue()) {
    dispatch_semaphore_signal(semaphore)
    view.setNeedsDisplay()
}
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC)))

という面倒なコードを書かなければいけなかった(この中に本当に重要なのはただの view.setNeedsDisplay() の 1 行だけだというのに…)が、自前で dispatch 系の struct を使うと

GCD.runAsynchronizedQueue(waitUntilStartForMax: 1) { 
    view.setNeedsDisplay()
}

のようにたったの3行で解決できちゃいます、しかも dispatch 用に何か新しい変数をいちいち自分で作る必要がなくなり、名前空間も効けば enum の活用でコードも短くなる上読みやすさも向上されます。まあ皆さんも自分のコードスタイルに合わせて dispatch をもっと使いやすいように何かしらの struct なり class なりを作ってみればマルチスレッドの制御がもっと書きやすくなると思いますよ!

宣伝:実は今プライベートで Swift の勉強や練習も兼ねて iOS 用の ADV エンジンを作ろうとしており、プロジェクトは GitHub 上で公開しています。完成にはまだほど遠いし master ブランチはまだ何もありませんし develop ブランチは今頑張って毎日更新しようとしているところですが出来てるのはまだ背景と立ち絵の表示だけですが。ちなみに最大の目標は読みやすいスクリプト文法を作ることです。というわけでおそらく ADV スクリプト文法としては初めてなんじゃないかな?という試みはスクリプト命令に変数ラベルを導入してみました。やはり Objective-C の最大の功績は変数ラベルですよ。はい。というわけで、実はこのプロジェクトがこの GCD struct を初めて使ってみたプロジェクトです。

9
9
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
9
9