观察 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 不能符合 ObservableObjectObservableObject 继承 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 结构体的变化的主要内容,如果未能解决你的问题,请参考以下文章

Swift中结构体的方法调度&内存分区

Swift 结构体和类的区别

Swift中结构体和类的区别

Swift之深入解析类和结构体的本质

结构体的 Swift 枚举

Swift Struct 结构体