【问题标题】:Error: 'async' call cannot occur in a property initializer错误:属性初始化程序中不能发生“异步”调用
【发布时间】:2021-10-23 09:33:00
【问题描述】:

我正在尝试在App 中放置一个StateObject actor,其初始值设定项为async,但我找不到这样做的方法。

假设我有这个演员:

actor Foo: ObservableObject {
    init() async {
        // ...
    }
}

这会导致名义上的错误:

import SwiftUI

struct MyApp: App {
    @StateObject
    var foo = await Foo() // ???? 'async' call cannot occur in a property initializer

    var body: some Scene {
        // ...
    }
}

这会导致类似的错误:

import SwiftUI

struct MyApp: App {
    @StateObject
    var foo: Foo

    init() async {
        _foo = .init(wrappedValue: await Foo()) // ???? 'async' call in a function that does not support concurrency
    }

    var body: some Scene {
        // ...
    }
}

即使这些也行不通:

import SwiftUI

struct MyApp: App {
    @StateObject
    var foo: Foo

    init() async {
        Task {
            self._foo = .init(wrappedValue: await Foo()) // ???? Mutation of captured parameter 'self' in concurrently-executing code
        }
    }

    var body: some Scene {
        // ...
    }
}
import SwiftUI

struct MyApp: App {
    @StateObject
    var foo: Foo

    init() async {
        Task { [self] in
            self._foo = .init(wrappedValue: await Foo()) // ???? Cannot assign to property: 'self' is an immutable capture
        }
    }

    var body: some Scene {
        // ...
    }
}

看来无论我做什么,我都不能让Foo 成为MyApp 的成员。我在这里想念什么?这当然是可能的。

我在使用 SwiftUI Views 时也遇到了同样的问题,所以任何适用于 Views 和 Apps 的建议都会非常棒!

【问题讨论】:

  • 这能回答你的问题吗? Initialize app with an Async function | SwiftUI
  • 可能有一个技术原因,为什么不能使用异步函数来初始化属性包装器,但在语义层面:异步初始化 StateObject 时的预期行为是什么?显示加载指示器或空白屏幕不起作用 - 您甚至没有任何状态可以在呈现视图时告诉该视图。那么,这有意义吗?将 View 视为 同步(纯)函数是有意义的。
  • @loremipsum 否,因为那里的答案在我最后的 2 个 sn-ps 中被解决为不可用。不过,谢谢!
  • @CouchDeveloper 我希望应用程序初始化,同时在后台启动一些服务。我很想用一种方法来反映 UI 的启动,但老实说,如果我可以阻止主线程等待它,只在它们完成启动后才显示 UI,我会没事的;那会比我现在只是编译器错误的更好
  • @KyLeggiero 你应该再看看它。您的 sn-ps 显示与 Apple 的意图背道而驰。 1. SwiftUI 可以随时重新初始化View,你的状态对象将是re initialized whenever SwiftUI decides,因为你是在init 中设置它,而不是直接在包装器中,也可能导致泄漏。 2.根据您上面的评论,这也是错误的方法,因为async 的重点是不阻塞主线程。

标签: swift async-await swiftui swift-concurrency


【解决方案1】:

我想告诉你如何解决这个问题:

当你想初始化某个需要较长时间的 State 值,因此你可能会异步执行它,你可以使用以下模式:

你从你的“状态”开始,它最初没有被初始化,比如 undefined。一种建模方法如下所示:

struct AppState<Value> {
    enum State {
        case undefined
        case initialising
        case idle(Value)
        case mutating(Value)
    }
    var state: State = .undefined
}

显然,您的 AppState 初始状态等于 undefined

现在,让我们假设,有一些东西会初始化它(稍后它将是您的视图模型)。当它即将被初始化时,它的状态会从 undefined 转换为 initialising

当它完成时,它的状态会从initialising转换到idle

请注意,这里没有什么“异步”——所有状态转换都是“瞬间”发生的。

您可能还注意到,还有一些附加值,仅在状态 idlemutating 中出现。这称为“扩展状态值”。

我还添加了一个“突变”状态,表明您可以在 AppState 上进行突变并相应地反映在您的变量 appState 中。

您可能已经感觉到,这种建模将我们引向某种“有限状态机”(FSM),它采用这种状态并在接收“输入”时执行突变。其中之一是“初始化”。

您的 ViewModel 现在可以实现这种有限状态机的必要缺失部分:

一组状态(如上)、一组输入(一个枚举)、一组输出(一个枚举)、一个转换函数(纯)和一个输出函数(纯),以及一个转换 AppState 的函数到一些在视图中呈现的 ViewState。用户操作将通过回调连接起来,最终作为输入值路由到系统中。输出值触发“副作用”,这正是您的异步​​“应用程序状态初始化”功能。副作用返回事件,这些事件再次被路由到系统的输入中。

【讨论】:

  • 我熟悉 FSM 并喜欢在我的应用程序中使用它们。这对actors 和async/await 有什么帮助?
  • 一个 FSM 是一个演员。还要注意,关键的想法是,你总是(在任何时候)你有一个定义的状态。因此,可以在任何时候相应地呈现此状态。状态机隐藏了一个事实,即存在异步函数 - 实际上是“副作用”。
  • 我不确定我是否遵循;您在回答中没有提到actors。您能否更新您的答案以显示我如何使用此解决方案来初始化我的 actor Foo 并在 SwiftUI 中使用,以便我可以将其传递到视图层次结构中,以便可以读取其字段并调用其成员?跨度>
  • 无论你是使用“Swift actor”还是使用 Combine(使用 receive(on:) 和 subscribe(on:))来确保线程安全并不重要。这是一个实现细节,即如何实现 FSM - 或 ViewModel。基本上,“异步初始化”没有意义。您可以在同步初始化后使用 Actors 修改其状态。您需要一个初始状态,就像 FSM 需要的一样。
  • 我不确定你在说什么。我正在尝试在 SwiftUI 应用程序中使用 actor,并且必须在某些时候初始化 actor,但我不清楚什么可以,也不知道什么时候可以。过去我使用过 GCD,但对于这个项目,我正在尝试学习如何使用actors。您能否更新您的答案以包括如何在 SwiftUI 应用程序中使用 actor,正如我的问题所问的那样?
猜你喜欢
  • 2021-04-03
  • 1970-01-01
  • 1970-01-01
  • 2020-10-30
  • 2019-12-03
  • 1970-01-01
  • 1970-01-01
  • 2021-10-11
  • 1970-01-01
相关资源
最近更新 更多