【问题标题】:SwiftUI + MVVM + async tasks in modelSwiftUI + MVVM + 模型中的异步任务
【发布时间】:2025-12-16 14:05:01
【问题描述】:

对于我想要实现的小型应用程序,我想使用 SwiftUI 并坚持使用 MVVM。

但是模型中的异步任务让我很头疼。

我创建了一个非常简单的例子来解释它:

查看

import SwiftUI  

struct ContentView: View {  

    @ObservedObject var viewModel: ViewModel  

    var body: some View {  
        VStack {  
            Text("\(viewModel.model.numberToDisplay)")  
            Button(action: {self.viewModel.model.increase()}) {  
                Text("increase")  
            }  
        }  
    }  

    struct ContentView_Previews: PreviewProvider {  
        static var previews: some View {  
            ContentView(viewModel: ViewModel())  
        }  
    }  
}  

视图模型

import Foundation  
import SwiftUI  

class ViewModel: ObservableObject {  
    @Published var model = Model()  
}  

型号

import Foundation  

struct Model {  

    private(set) var numberToDisplay: Int = 0  

    mutating func increase() {  
        for _ in 0...2 {  
            self.numberToDisplay += 1  
            sleep(1)  
        }  
    }  
}  

有了这个,用户界面将在开始时显示“0”。点击“increase”会阻止 UI 3 秒(参见模型中的 for 循环),然后会显示“3”。

我想要的是一个非阻塞的 UI,并且显示 numberToDisplay 的所有更新(0 -> 1 -> 2 -> 3 而不是 0 -> 3)。

如果我尝试在模型中使用这样的 DispatchQueue:

DispatchQueue.global().async {  
     self.numberToDisplay += 1  
}  

它只是给了我一个“转义闭包捕获变异 'self' 参数”错误。

对模型使用类而不是结构允许我使用 DispatchQueue,但更糟糕的是,UI 根本没有更新。 (DispatchQueue 的使用在这里没有任何作用。)

那么在模型中使用 SwiftUI 与 MVVM 和异步函数的合适方法是什么?

最好的问候 只是亚历克斯

【问题讨论】:

  • 不要使用 MVVM,使用 SwiftUI

标签: asynchronous mvvm swiftui


【解决方案1】:

这只是一个责任问题......如果将模型视为数据的被动存储,那么这里是可能的解决方案。

使用 Xcode 11.4 / iOS 13.4 测试

struct ContentView: View {

    @ObservedObject var viewModel: ViewModel

    var body: some View {
        VStack {
            Text("\(viewModel.model.numberToDisplay)")
            Button(action: {self.viewModel.increase()}) {
                Text("increase")
            }
        }
    }
}

class ViewModel: ObservableObject {
    @Published var model = Model()

    func increase() {
        DispatchQueue.global(qos: .background).async {
            for _ in 0...2 {
                DispatchQueue.main.async {
                    self.model.numberToDisplay += 1
                }
                sleep(1)
            }
        }
    }
}

struct Model {
    var numberToDisplay: Int = 0
}

注意:...我想说这还不是结束,因为numberToDisplay在语义上不能成为模型的一部分,属于视图模型层,所以你可以继续... em>

【讨论】:

  • 您好 Asperi,感谢您的回复。当然,这个“numberToDisplay”的命名方式只是为了更好地表达整个视图中的关系。你提出的工作。但是这样业务逻辑现在在 ViewModel 中。通常我会将业务逻辑放在模型本身中。 BR只是亚历克斯
【解决方案2】:
DispatchQueue.global().async {  
    self.numberToDisplay += 1 // you can't mutate a struct without mutating function
    self.increase() // may work
}

如果您将模型更改为引用类型,即;类,即使你改变它的属性,引用本身也不会改变,因为引用只是指向一些内容被修改的内存,而不是引用本身。

因此您的观察者将无法使用引用类型模型。

【讨论】: