观察 Swift 结构体的变化
Posted
技术标签:
【中文标题】观察 Swift 结构体的变化【英文标题】:Observing Changes to Struct Swift 【发布时间】:2021-11-06 08:23:51 【问题描述】:我希望我的 MVVM 架构有一个作为模型的类和一个作为视图模型或视图控制器的结构。这是因为我想通过更改模型重新初始化一堆属性,并且使用一个类作为视图模型只允许一个初始化。但是,它还没有找到一种方法让视图观察视图模型中的新结构/结构的变化。
我的问题示例:
class Model: ObservableObject
var nums: [Int]
init()
self.nums = Array(1..<100)
func getNum() -> Int
return nums.count
func add()
nums.append(nums.count + 1)
self.objectWillChange.send()
struct ViewModel
var model: Model
var num: Int
init(model: Model)
self.model = model
self.num = model.getNum()
func trigger()
model.add()
print("Triggered")
struct ContentView: View
var viewModel: ViewModel
var body: some View
Button(action: viewModel.trigger() )
Text("Press")
Text("Number of Elements")
Text("\(viewModel.num)")
var model = Model()
var viewModel = ViewModel(model: model)
var view = ContentView(viewModel: viewModel)
@main
struct app: App
var body: some Scene
WindowGroup
view
【问题讨论】:
你的ViewModel
的初始化器改变了Model
。这似乎很糟糕。为什么要显示好友数修改好友数?
正如上面的评论所暗示的,这似乎是一个奇怪的例子。 num
永远不会在视图模型的初始化程序之外发生变异。我对这里期望的行为感到困惑。一般来说,这种方法与您的模型的传统路径是 struct
和您的视图模型是 class
的传统路径相反
我已经编辑了示例以避免在 init 中改变数组
Num 确实通过按钮在视图级别发生了变异
不,它没有。 nums
会。
【参考方案1】:
嗯,我想我看到的第一件事是你将可观察对象放入模型中,而在 mvvm 中它试图将该属性放入视图模型中,因为它需要是反应性的,模型只接收一些属性,您可以将属性更改为其视图模型并使其具有反应性。
如果你使用一个类作为模型没关系,它仍然可以工作,这取决于你的喜好。
class Model
var nums: [Int]
init()
self.nums = Array(1..<100)
func getNum() -> Int
return nums.count
func add()
nums.append(nums.count + 1)
self.objectWillChange.send()
struct ViewModel:ObservableObject
var model: Model
var num: Int
init(model: Model)
self.model = model
self.num = model.getNum()
func trigger()
model.add()
print("Triggered")
struct ContentView: View
@observedObject var viewModel: ViewModel
var body: some View
Button(action: viewModel.trigger() )
Text("Press")
Text("Number of Elements")
Text("\(viewModel.num)")
var model = Model()
var viewModel = ViewModel(model: model)
var view = ContentView(viewModel: viewModel)
@main
struct app: App
var body: some Scene
WindowGroup
view
【讨论】:
结构不能符合 ObservableObject,对吧? 是的,它可以符合它没有任何问题 不,struct
不能符合 ObservableObject
。 ObservableObject
继承 AnyObject
,这意味着只有类(和参与者)才能符合 ObservableObject
。
@robmayoff 对此感到抱歉,我没有注意到,但你是对的,我从来没有在课堂上做过测试! ?【参考方案2】:
如果您的主要目标是根据模型的更改在视图模型上计算属性,我将采用以下方法解决此问题:
import Combine
struct Model
var nums: [Int]
init()
self.nums = Array(1..<100)
func getNum() -> Int
return nums.count
//note there aren't mutating methods here
class ViewModel : ObservableObject
@Published var model: Model
@Published var num: Int = -1
private var cancellable : AnyCancellable?
init(model: Model)
self.model = model
cancellable = $model.sink(receiveValue: newValue in
self.num = newValue.getNum() //calculated based on the new value
)
func trigger()
self.add()
print("Triggered")
//mutating method here
func add()
model.nums.append(model.nums.count + 1)
struct ContentView: View
@StateObject var viewModel: ViewModel = ViewModel(model: Model())
var body: some View
Button(action: viewModel.trigger() )
Text("Press")
Text("Number of Elements")
Text("\(viewModel.num)")
更新,如果你想要模型中的变异函数:
struct Model
var nums: [Int]
init()
self.nums = Array(1..<100)
func getNum() -> Int
return nums.count
mutating func add()
nums.append(nums.count + 1)
class ViewModel : ObservableObject
@Published var model: Model
@Published var num: Int = -1
private var cancellable : AnyCancellable?
init(model: Model)
self.model = model
cancellable = $model.sink(receiveValue: newValue in
self.num = newValue.getNum() //calculated based on the new value
)
func trigger()
model.add()
print("Triggered")
【讨论】:
我们需要在 viewModel 中有 mutating 方法吗?在我的实际问题中,变异逻辑足够复杂,以至于我想将其封装在模型中(或者我正在调用模型,此时我不处理 API 请求等)。 查看我的更新。就个人而言,我不会以这种方式处理它,因为它有些不合常规,但可以做到。 作为一项改进,我不会通过 ViewModel 泄漏模型(而是发布包含视图所有相关状态的专用结构),并在视图模型中提供操作方法(从 UI 调用)这反过来可能会改变底层模型。那么如何执行突变是 ViewModel 的一个实现细节。【参考方案3】:这里有一些非常简单的东西,可以完成我想要的。不要使用结构作为视图模型/控制器,而是使用类——现在模型和视图模型都是类。当然,这样做的问题是类是引用类型,并且不会像结构一样重新初始化,结构是通过更改重新创建的。所以我不依赖 init 方法来设置我的属性——在我知道trigger
类的trigger
方法发生变化后,我会更新它们。这是一个例子:
class Model
var nums: [Int]
init()
self.nums = Array(1..<100)
func getNum() -> Int
return nums.count
func add()
nums.append(nums.count + 1)
func updateProperty(model: Model) -> Int
return model.getNum()
class ViewModel: ObservableObject
var model: Model
var num: Int
init(model: Model)
self.model = model
self.num = updateProperty(model: model)
func trigger()
model.add()
self.num = updateProperty(model: self.model)
self.objectWillChange.send()
struct ContentView: View
@ObservedObject var viewModel: ViewModel
var body: some View
Button(action: viewModel.trigger() )
Text("Press")
Text("Number of Elements")
Text("\(viewModel.num)")
var model = Model()
var viewModel = ViewModel(model: model)
var view = ContentView(viewModel: viewModel)
显然,在大多数实际情况下,updateProperty
函数将比一个 Int
更复杂
【讨论】:
以上是关于观察 Swift 结构体的变化的主要内容,如果未能解决你的问题,请参考以下文章