こんにちは。iOS開発担当の洪です。
この記事はGunosy Advent Calendar 2021の22日目の記事です。 昨日の記事は茂木さんの2021 年の SRE チームの活動についてでした。大変面白い記事ですので是非読んでみてください。
- はじめに
- Swift Concurrencyは簡単ではない
- Swift Concurrencyはただではない
- Swift5.5のSwift Concurrencyはまだ完成版ではない
- iOSフレームワークの関連メソッドはまだiOS15以上のまま
- One more thing
- おわりに
はじめに
2021年6月のWWDCでSwift Concurrencyの発表がありました。
その後、そのSwift ConcurrencyはiOS15以上のみで利用可能と判明されました。
そして2021年12月、Xcode13.2からiOS13以上でSwift Concurrencyが利用可能になりました。
ここではSwift Concurrencyでちょっとだけコードを書いてみて感じた気になるところをまとめてみました。 (開発環境はXcode13.2.1)
Swift Concurrencyは簡単ではない
Swift ConcurrencyはJavaScriptのasync/awaitのようなPromiseのシンタックスシュガーではありません。以下の色んな新しい機能の集合体で、ちゃんと使うにはGrand Central Dispatch(GCD)以上の学習が必要です。
- async/awaitによる一時停止と再開が可能な順次実行
Asynchronous code can be suspended and resumed later, although only one piece of the program executes at a time.
https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html
- Taskによる並列処理
Parallel code means multiple pieces of code run simultaneously—for example, a computer with a four-core processor can run four pieces of code at the same time, with each core carrying out one of the tasks.
https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html
Swift Concurrencyはただではない
WWDCのSwift concurrency: Behind the scenesで説明されてるようにConcurrencyの利用にはそれなりのコストが発生します。
そしてアプリのサイズも若干ですが増加します。(多分iOS14以下のみ?)
最後にまだリリースされたばかりでどんなバグがあるかも不明です。
Swift5.5のSwift Concurrencyはまだ完成版ではない
Concurrency in Swift 5 and 6によると今後リリース予定のSwift6ではSendable
のチェック強化なので、もっと安全なConcurrencyを目指していてSwift5.5からの移行にはそれなりの対応が必要になりそうです。
Swift5.5でも以下のフラグ設定で確認できるのでSwift Concurrencyの導入後、一度確認してみたいと思います。
-Xfrontend -warn-concurrency
-Xfrontend -enable-actor-data-race-checks
(in Xcode: Other Swift Flags)
https://twitter.com/olebegemann/status/1421144304127463427
iOSフレームワークの関連メソッドはまだiOS15以上のまま
SwiftとしてはiOS13以上でSwift Concurrencyが利用可能になりましたが、iOSの各フレームワークの関連メソッドはまだiOS15以上のままです。
Making async system APIs backward compatibleなどでの説明のように自分でコツコツとラッパを作成するか、既存コードがある場合は次に説明するリファクタリングツールで変換しながら対応する必要があります。
One more thing
Xcodeのリファクタリング機能にConvert Function to Async
, Add Async Alternative
, Add Async Wrapper
が追加されているので、既存コードのSwift Concurrencyへの移行時に役に立ちそうです。
せっかくなので以下のサンプルコードで実行してみました。
func fetchData(url: URL, completionHandler: @escaping (Result<Data, Error>) -> Void) { URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { completionHandler(.failure(error)) } else { completionHandler(.success(data!)) } }.resume() }
- Convert Function to Asyncの実行後のコード
func fetchData(url: URL) async throws -> Data { return try await withCheckedThrowingContinuation { continuation in URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { continuation.resume(with: .failure(error)) } else { continuation.resume(with: .success(data!)) } }.resume() } }
- Add Async Alternativeの実行後のコード
@available(*, renamed: "fetchData(url:)") func fetchData(url: URL, completionHandler: @escaping (Result<Data, Error>) -> Void) { Task { do { let result = try await fetchData(url: url) completionHandler(.success(result)) } catch { completionHandler(.failure(error)) } } } func fetchData(url: URL) async throws -> Data { return try await withCheckedThrowingContinuation { continuation in URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { continuation.resume(with: .failure(error)) } else { continuation.resume(with: .success(data!)) } }.resume() } }
- Add Async Wrapperの実行後のコード
@available(*, renamed: "fetchData(url:)") func fetchData(url: URL, completionHandler: @escaping (Result<Data, Error>) -> Void) { URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { completionHandler(.failure(error)) } else { completionHandler(.success(data!)) } }.resume() } func fetchData(url: URL) async throws -> Data { return try await withCheckedThrowingContinuation { continuation in fetchData(url: url) { result in continuation.resume(with: result) } } }
おわりに
色々書きましたが、Swift Concurrencyは素晴らしい技術で、これからのiOS開発を支える柱になると思っています。 みんなで全集中常中でConcurrencyの呼吸が使えるように鍛錬していきましょう!
それでは明日の記事もお楽しみに!