14
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftAdvent Calendar 2022

Day 5

Swiftにしかないasync functionとその活用方法の考察

Last updated at Posted at 2022-12-08

「Swiftにしかないasync function」って?

この一文を見たみなさんは
「いやいや、JavaScript(や他言語)にもasync functionはあるよw」
と思われたことでしょう。

ですが
それらの言語の「async function」はSwiftの「async function」とは異なります。

Swiftの「async function」と「async throws function」

Swiftには「async function」と「async throws function」があります。

// throws function
func hogeThrows() throws -> {}

// async function
func hogeAsync() async -> {}

// async throws function
func hogeAsyncThrows() async throws -> {}

Swiftには検査例外があり「失敗する関数=throws function」が存在していました。

そこに、Swift 5.5で「非同期関数=async function」が実装されました。
重要なのは、「async」は「throws」を含有しておらず、それぞれ独立に関数に付与できるという点です。
つまり、Swiftの「async function」は検査例外を出しません。つまり「失敗しない非同期関数」です。
そして、Swiftの「async throws function」がJavaScript等の言語の「async function」に当たり、「失敗する非同期関数」となります。

以上より、Swiftにしかないasync function という意味がわかったかと思います。

async functionの良さと活用方法

「失敗しない非同期関数」は何が良いのでしょうか?
それは、呼び出し時にエラーハンドリングを必要としない点です。

実際に活用されている場所を見るのが一番分かりやすいと思います。

活用例1: refreshable

定義

func refreshable(action: @escaping () async -> Void) -> some View

使用例

List(mailbox.conversations) { conversation in
    ConversationCell(conversation)
}
.refreshable {
    await mailbox.fetch()
}

これは、SwiftUIでリスト表示のいわゆる「プルリフ」を実装するメソッドです。
このメソッド定義には表示状態を管理する isRefreshing: Binding<Bool> がなく、リフレッシュ開始時に実行する関数action: @escaping () async -> Void しかありません。
ではどうやって表示終了を指定するのでしょう?

もうお気づきかもしれませんが、そうです、リフレッシュ開始時に実行する関数が「async function」となっているので、 この関数がawaitしている間、アニメーションが表示され続ける という仕様になっています。

The indicator remains visible for the duration of the refresh, which runs asynchronously:

実用例としては以下のようになります。

@MainActor
final class ViewModel: ObservableObject {
    let model: Model

    @Published var datas: [SomeData] = []
    
    func onRefresh() async {
        do {
            datas = try await model.fetchSomeDatas()
        } catch {
                // エラーハンドリング
        }
    }
}

// View
List(viewModel.datas) {...}
.refreshable {
    await viewModel.onRefresh()
}

Modelの状態ではasync throwsなのを、ViewModelでthrowsを取り除いてasyncにし、失敗しない状態でViewに公開することができます。
この、非同期処理の待機をViewの描画に安全に利用できる、というのがSwiftのasync functionにしかできない強み だと思います。

活用例2: CBCentralManager

過去に書いた記事で、「Bluetoothの許可アラートが表示されている間は待機し、許可された後に処理を実行したい」というケースを紹介しました。これは「失敗しない非同期処理」でした。

活用例: 補足

async/awaitがSwiftに来る前に使っていた人も多いであろう、Swift向けPromiseライブラリの PromiseKit にも、Guarantee という「失敗しないPromise」が存在しました。

前から「失敗しない非同期処理」の需要があったということだと思います。

まとめ

Swiftにあった検査例外は元々素晴らしい言語仕様でしたが、async/awaitの登場によってさらに検査例外の素晴らしさが増した共に、他の言語にはない「失敗しない非同期処理」という概念と活用例を生み出しました。
活用例で紹介したrefreshableのような使い方が、今後もっと開拓されていくと面白いなと思います。

余談

Swift以外の言語を使っていて、「実装で多分失敗しないことがわかっている(=例外を投げていない)のに、言語上例外が出る可能性があるから、クラッシュしないようにエラーハンドリングしておくか...」と渋々try-catchを書いた方もいるのではないでしょうか。
Kotlinなんかは、Javaが検査例外があるんだからいい感じにラップして検査例外を実装すればいいのにと思ったりしています。

14
5
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
14
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?