Abstract
現在iOSの非同期処理は一般的にGCDを使うのです、closure或いはdelegateでコールバックして、処理を追うんだが。関係性がある複数の非同期処理を実行する場合、コールバック地獄になる。
例えば、以下の画像処理のコードがある。
func fetchImage(url: String, completeHandler: @escaping (UIImage) -> ()) {
...
completeHandler(image)
}
func imageDetectFace(image: UIImage, completeHandler: @escaping (UIImage) -> ()) {
...
completeHandler(image)
}
func imageAddFilter(image: UIImage, completeHandler: @escaping (UIImage) -> ()) {
...
completeHandler(image)
}
使う時は以下のようになる。
fetchImage(url: "hoge/url") { (originImage) in
imageDetectFace(image: originImage) { (faceImage) in
imageAddFilter(image: faceImage) { (filterImage) in
imageView.image = filterImage
}
}
}
処理を増えると、可読性とメンテナンス性がどんどん下がる。
Introduction
上記の問題の解決策として、RxSwiftとCombineなどのライブラリを使って、リアクティブプログラミングでコールバックを撲滅するのが一番多いですが。リアクティブプログラミングのデメリットとして、debugが大変なので。
今回はcoobjcというライブラリを使って、Coroutine風に非同期処理してみる。
Coobjc
Coobjcは中国のアリババグループがOSSしたiOS用のCoroutineライブラリです。このライブラリはアセンブリとCで開発して、Objc-CとSwiftをサポートしている。このライブラリを通して、kotlin、JavaScriptやPythonなどみたいに、Coroutine風でプログラミングできる。
Github: https://github.com/alibaba/coobjc
Usage
coobjcはAsync/AwiatとGeneratorとActorなどのCoroutine機能をサポートしている。今回はAsync/Awiatだけ使ってみる。
Async/Awiat
上記のコールバック地獄のコードを、Async/Awiatを使ってCoroutine風に書き直すと、↓になる。
func fetchImage(url: String) -> Promise<UIImage> {
return Promise(on: DispatchQueue.global()) { (fullFill, reject) in
fullFill(UIImage())
}
}
func imageDetectFace(image: UIImage) -> Promise<UIImage> {
return Promise(on: DispatchQueue.global()) { (fullFill, reject) in
fullFill(UIImage())
}
}
func imageAddFilter(image: UIImage) -> Promise<UIImage> {
return Promise(on: DispatchQueue.global()) { (fullFill, reject) in
fullFill(UIImage())
}
}
使う時は以下のようになる。
co_launch(queue: DispatchQueue.main) {
var image: UIImage = UIImage()
let imgResult = try await(promise: self.fetchImage(url: "url"))
if case .fulfilled(let fetchedImg) = imgResult {
image = fetchedImg
} else {
return
}
let faceResult = try await(promise: self.imageDetectFace(image: image))
if case .fulfilled(let faceImg) = faceResult {
image = faceImg
} else {
return
}
let filterResult = try await(promise: self.imageAddFilter(image: image))
if case .fulfilled(let filterImg) = filterResult {
imageView.image = filterImg
}
}
これで非同期処理が終わったらMainThreadに戻り、imageViewを更新する。GCDとコールバックもすっきりになりました。
Conclusion
もしiOSで、Coroutine風にプログラミングしたい場合は、Coobjcが結構役に立てると思いますけど、プロジェクトへの侵入性がちょっと強くて、やめたい時は面倒になる。