まあ皆さんも多分思ってるじゃないでしょうか、バックグラウンドスレッドに切り替えたり逆にメインスレッドを呼び出したりすることがよくありますが、dispatch
系、もしくはは GCD 系の文法ってなんでいちいちそんなに書くの面倒でしょう、と。
まあ GCD 系の命令は純粋な C 言語の物なので、名前空間はもちろん、クラスなんざも当然ないし、ただの関数だから引数ラベルなんかもあるわけない。そのせいで関数名は全て dispatch_
なんちゃらになるから、今の Xcode 7.3 より古い Xcode なら補完効かせるのもすごいしんどかったです。7.3 からは途中入力も効くようになったからだいぶ助かりましたが。
それでも dispatch
系の関数を利用するのに、いちいち dispatch
系の変数を作らなきゃいけなかったりと利用するのに一手間かかるものです…
そうだ!GCD struct を作ろう
というわけで、作ってみた結果がこれです:
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
を初めて使ってみたプロジェクトです。