如何使这段代码支持并发安全?

huangapple go评论79阅读模式
英文:

How can I make this code concurrency safe?

问题

我有以下用Swift编写的代码

```swift
protocol Theme {
    var iconColor: Color { get }
}

struct MyTheme: Theme {
   var iconColor: Color = .white
}

class Appearance {
    static var theme: Theme = MyTheme()
}

我可以在我的视图中使用这个Appearance类来方便地获取当前主题的属性:

Image("my-icon")
    .foregroundColor(Appearance.theme.iconColor)

我在XCode中启用了-warn-concurrency编译标志,作为一个实验,看看我的应用程序会产生多少警告。这是其中一个警告:

Reference to static property 'theme' is not concurrency-safe because it involves shared mutable state

现在我明白为什么会出现这个警告,但我该如何更改代码,以使theme在并发环境中是安全的,同时仍然像现在这样方便?

我可以将theme更改为static let,但那样我将无法在运行时更改应用程序的主题。我还尝试了@MainActoractor Appearance的组合,但它们要么产生相同的警告,要么产生其他错误。

编辑

@MainActor class Appearance可以消除所有警告,但请考虑以下情况:

struct FooView: View {
    var iconColor: Color = Appearance.theme.iconColor
    
    var body: some View {
        HStack {
            Text("FooView")
            Image("foo-logo")
                .foregroundColor(iconColor)
        }
    }
}

上面的代码会产生错误:Main actor-isolated static property 'theme' cannot be referenced from a non-isolated context


<details>
<summary>英文:</summary>

I have the following code written in Swift:


protocol Theme {
var iconColor: Color { get }
}

struct MyTheme: Theme {
var iconColor: Color = .white
}

class Appearance {
static var theme: Theme = MyTheme()
}


I can use this `Appearance` class in my views to conveniently get the current theme&#39;s properties


Image("my-icon")
.foregroundColor(Appearance.theme.iconColor)


I enabled `-warn-concurrency` compiler flag in XCode as an experiment to see how many warnings my app produces. This is one of the warnings: 

`Reference to static property &#39;theme&#39; is not concurrency-safe because it involves shared mutable state`

Now I understand why it gives this warning, but how could I change the code so that `theme` is concurrency safe and still as convenient as it is?




I could change `theme` to `static let` but then I would not be able to change the theme of the app during runtime. I also tried combinations of `@MainActor` and `actor Appearance` but they give mixed result with either the same warning or some other error.

**Edit**

`@MainActor class Appearance` gets rid of all the warnings, but consider this:

struct FooView: View {
var iconColor: Color = Appearance.theme.iconColor

var body: some View {
    HStack {
        Text(&quot;FooView&quot;)
        Image(&quot;foo-logo&quot;)
            .foregroundColor(iconColor)
    }
}

}


The code above will give an error: `Main actor-isolated static property &#39;theme&#39; can not be referenced from a non-isolated context`

</details>


# 答案1
**得分**: 2

要消除警告,您只需将`@MainActor`添加到`Appearance`类中。如果您在`body`之外使用`FooView`,还需要将其标记为`@MainActor`,因为`body`是`MainActor`隔离的,但`FooView`不是。

话虽如此,这在实践中不会起作用。SwiftUI 不会知道`Appearance.theme`已更改并相应地更新您的视图颜色。在 SwiftUI 中实现这样的“主题”系统的方式是使用`EnvironmentObject`。

```swift
class Appearance: ObservableObject {
    @Published var theme: Theme = MyTheme()
}
struct YourRootView: View {
    // 在每个需要访问主题的视图中编写此代码
    @EnvironmentObject var appearance: Appearance
    
    var body: some View {
        Text("Foo")
            .foregroundColor(appearance.theme.iconColor)
    }
}

当您创建YourRootView时,请为其提供一个新的Appearance对象。

@main
struct FooApp: App {
    var body: some Scene {
        WindowGroup {
            YourRootView()
                .environmentObject(Appearance())
        }
    }
}

现在 SwiftUI 可以观察theme属性的更改。

英文:

To remove the warning, you can just add @MainActor to the Appearance class. You would also need to mark FooView with @MainActor if you are using it outside of body, because body is MainActor-isolated, but FooView is not.

That said, this won't work in practice. SwiftUI won't know that Appearance.theme has changed and update your views' colors accordingly. The SwitUI way of implementing such a "theme" system is to use an EnvironmentObject.

class Appearance: ObservableObject {
    @Published var theme: Theme = MyTheme()
}
struct YourRootView: View {
    // write this in every view that needs to access the theme
    @EnvironmentObject var appearance: Appearance
    
    var body: some View {
        Text(&quot;Foo&quot;)
            .foregroundColor(appearance.theme.iconColor)
    }
}

When you create YourRootView, give it a new Appearance object.

@main
struct FooApp: App {
    var body: some Scene {
        WindowGroup {
            YourRootView()
                .environmentObject(Appearance())
        }
    }
}

Now SwiftUI can observe the changes to the theme property.

huangapple
  • 本文由 发表于 2023年7月27日 18:38:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76778913.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定