kota1021
@kota1021 (松本 幸太郎)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Viewの更新はメインスレッドから行うべき?

知りたいこと

Appleの動画で、asyncにviewを更新するコードが気になって、詳細が知りたい。

この動画の20分あたりで以下のコードが出てきます。

私の理解としては、Taskは同期的な文脈の中で非同期的な文脈を作り出すもので、その内部では非同期的な処理をawaitで呼び出せる、というふうに捉えています。
そして非同期的な文脈では、コードの1行1行がどのスレッドで実行されるかは不確定で、メインスレッド以外から呼ばれる可能性もあると見聞きしました。

以下のコードではどのようにisSavingへの代入がメインスレッドから行われることを保証しているのでしょうか?

SavePhotoButton.swift
struct SavePhotoButton: View {
    var photo: SpacePhoto
    @State private var isSaving = false

    var body: some View {
        Button {
            Task {
                isSaving = true //ここ1
                await photo.save()
                isSaving = false//ここ2
            }
        } label: {
            Text("Save")
                .opacity(isSaving ? 0 : 1)
                .overlay {
                    if isSaving {
                        ProgressView()
                    }
                }
        }
        .disabled(isSaving)
        .buttonStyle(.bordered)
    }
}
0

1Answer

Task {}は呼ばれた場所でのActorコンテキストを受け継ぎます。

var body: some View

はMainActorなので、Task内はメインスレッドで実行されます。
bodyがMainActorというのは、
スクリーンショット 2023-06-13 15.06.58.png
このようにView Protocolに記されている通りです。

ですが、

var body: some View {
    button
}

var button: some View {
    Button("ボタン") {
        Task {
            // ここはメインスレッド以外で実行される可能性があります。
        }
    }
}

var button: some ViewはMainActorを指定していませんので、Taskはメインスレッド以外で実行される可能性があります。
なので、

@MainActor
var button: some View {
    Button("ボタン") {
        Task {
            // メインスレッドで実行される
        }
    }
}

このようにしたらメインスレッドで実行されます。

ちなみに、

var body: some View {
    Button("ボタン") {
        Task.detached {
            // メインスレッド以外で実行される
        }
    }
}

このようにすると、親のActor contextを引き継がないためメインスレッド以外で実行されます。

1Like

Comments

  1. @kota1021

    Questioner

    詳細でわかりやすいご回答をしていただき、ありがとうございます。
    bodyがMainActorなことを知りませんでした。
    また、protocol上でproperty wrapperを宣言して、それに準拠したclass/structにそのpropertyを実装する際、property wrapperを書かなくてもあるものとみなされることも初めて知りました。
    Task.detached {}では、親のActor contextを引き継がないのですね。

    まだまだ勉強が必要だと痛感しました。
    ありがとうございます😊

  2. ありがとうございます!
    ちなみになのですが、protocolではpropertyWrapperは使用できません。
    なので、@ViewBuilder@MainActorはPropertyWrapperとは別の"Attribute"というものとなっています。

  3. @kota1021

    Questioner

    訂正していただきありがとうございます。
    いただいた情報をもとにAttributeを調べてみました。
    @ViewBuilder@resultBuilderとマークされ宣言されたリザルトビルダで、
    @MainActor@globalActorとマークされ宣言されたGlobalActor protocolに準拠するアクターなんですね。
    (まだまだ理解が不十分ですが…)
    https://www.swiftlangjp.com/language-guide/advanced-operators.html#result-builders
    https://developer.apple.com/documentation/swift/globalactor
    これからも勉強頑張ります🔥

Your answer might help someone💌