0

我有一个像这样的演员,他在后台不断地执行冗长而复杂的工作:

actor Foo {
    var field: [Bar]

    struct Bar {
        // ...
    }
}

如何field从 SwiftUI 视图更新它?

我试过这个,但得到了这些错误:

import SwiftUI

struct MyView: View {
    @StateObject
    var foo: Foo


    var body: some View {
        Text("Field count is \(foo.field.count)") //  Actor-isolated property 'field' can not be referenced from the main actor

        Button("Reset foo") {
            foo.field = [] //  Actor-isolated property 'field' can not be mutated from the main actor
        }
    }
}

如何actor从 SwiftUI 视图中访问和改变我的?

4

1 回答 1

3

访问 actor 的属性的问题在于,如果访问是在 actor 之外进行的field,它需要并调用。await这意味着您的 SwiftUI 代码中有一个暂停点,这意味着当 SwiftUI 代码恢复时,它可能不再在主线程上执行,这是一个大问题。

如果演员不做后台工作,那么 Asperi 使用 @MainAction 的解决方案会很好地工作,因为在这种情况下,SwiftUI 访问发生在主线程上。

但是如果actor在后台运行,您需要另一个同步点,它在主线程上运行代码,包装Fooactor,并由您的视图使用:

actor Foo {
    private(set) var field: [Bar]
    
    func updateField(_ field: [Bar]) {
        self.field = field
    }

    struct Bar {
        // ...
    }
}

class FooModel: ObservableObject {
    private let foo: Foo
    
    @Published var field: [Foo.Bar] = [] {
        didSet {
            Task { await foo.updateField(field) }
        }
    }
    
    init(foo: Foo) {
        self.foo = foo
        Task { self.field = await foo.field }
    }
}

struct MyView: View {
    @StateObject
    var foo: FooModel

然而,这只是故事的一半,因为您还需要在值更改时Foo发送通知。您可以为此使用 a:FooModelfieldPassthroughSubject

actor Foo {
    var field: [Bar] {
        didSet { fieldSubject.send(field) }
    }
    
    private let fieldSubject: PassthroughSubject<[Bar], Never>
    let fieldPublisher: AnyPublisher<[Bar], Never>
    
    init() {
        field = ... // initial value
        fieldSubject = PassthroughSubject()
        fieldPublisher = fieldSubject.eraseToAnyPublisher()
    }
    
    func updateField(_ field: [Bar]) {
        self.field = field
    }

    struct Bar {
        // ...
    }
}

并订阅模型发布的内容:

class FooModel: ObservableObject {
    private let foo: Foo
    
    @Published var field: [Foo.Bar] = [] {
        didSet {
            Task { await foo.updateField(field) }
        }
    }
    
    init(foo: Foo) {
        self.foo = foo
        Task { self.field = await foo.field }
        foo.fieldPublisher.receive(on: DispatchQueue.main).assign(to: &$field)
    }
}

如您所见,由于actor在任意线程上运行,而您的SwiftUI代码(或任何一般的UI代码)必须仅在主线程上运行,因此需要编写大量代码。

于 2021-08-27T06:13:37.020 回答