Swift:ViewModel 应该是结构还是类?
Posted
技术标签:
【中文标题】Swift:ViewModel 应该是结构还是类?【英文标题】:Swift: Should ViewModel be a struct or class? 【发布时间】:2016-10-21 04:48:38 【问题描述】:我正在尝试在我的新项目中使用 MVVM 模式。第一次,我创建了我所有的视图模型来构建。但是当我使用闭包实现异步业务逻辑(例如 fetchDataFromNetwork)时,闭包会捕获旧的视图模型值,然后更新为该值。不是新的视图模型值。
这是 Playground 中的测试代码。
import Foundation
import XCPlayground
struct ViewModel
var data: Int = 0
mutating func fetchData(completion:()->())
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://***.com")!)
result in
self.data = 10
print("viewModel.data in fetchResponse : \(self.data)")
completion()
XCPlaygroundPage.currentPage.finishExecution()
.resume()
class ViewController
var viewModel: ViewModel = ViewModel()
didSet
print("viewModel.data in didSet : \(viewModel.data)")
func changeViewModelStruct()
print("viewModel.data before fetch : \(viewModel.data)")
viewModel.fetchData
print("viewModel.data after fetch : \(self.viewModel.data)")
var c = ViewController()
c.changeViewModelStruct()
控制台打印
viewModel.data before fetch : 0
viewModel.data in didSet : 0
viewModel.data in fetchResponse : 10
viewModel.data after fetch : 0
问题是 ViewController 中的 View Model 没有新的 Value 10。
如果我将 ViewModel 更改为 class,则不会调用 didSet,但 ViewController 中的 View Model 具有新值 10。
【问题讨论】:
【参考方案1】:你应该使用一个类。
如果您使用带有变异函数的结构,则该函数不应在闭包内执行变异;您应该不执行以下操作:
struct ViewModel
var data: Int = 0
mutating func myFunc()
funcWithClosure()
self.data = 1
如果我将 ViewModel 更改为 class,则不会调用 didSet
这里没有错 - 这是预期的行为。
如果你喜欢使用struct
,你可以这样做
func fetchData(completion: ViewModel ->())
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://***.com")!)
result in
var newViewModel = self
newViewModel.data = 10
print("viewModel.data in fetchResponse : \(self.data)")
completion(newViewModel)
XCPlaygroundPage.currentPage.finishExecution()
.resume()
viewModel.fetchData newViewModel in
self.viewModal = newViewModel
print("viewModel.data after fetch : \(self.viewModel.data)")
还要注意,提供给dataTaskWithURL
的闭包不会在主线程上运行。您可能想在其中调用dispatch_async(dispatch_get_main_queue()) ...
。
【讨论】:
所以没有办法使用带有异步API调用@Code的struct?因为我更喜欢使用结构而不是类。 @Paul 编辑了我的帖子(再次)。 是的,这是一个糟糕的设计。 :( 在这种情况下我应该使用类。谢谢@Code。 尽管它的设计很糟糕,但使用类,编写单元测试要容易得多。 (struct 的 Mocking 方法确实是一项令人沮丧的任务)【参考方案2】:您可以通过两种方式获得self.data
:或者在闭包中为fetchResponse
使用返回参数(使用viewModel
作为struct
)或者您可以创建自己的设置方法/闭包并使用它在你的init
方法中(使用viewModel
作为class
)。
class ViewModel
var data: Int = 0
func fetchData(completion:()->())
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://***.com")!)
result in
self.data = 10
print("viewModel.data in fetchResponse : \(self.data)")
completion()
XCPlaygroundPage.currentPage.finishExecution()
.resume()
class ViewController
var viewModel: ViewModel! didSet print("viewModel.data in didSet : \(viewModel.data)")
init( viewModel: ViewModel )
// closure invokes didSet
( self.viewModel = viewModel )()
func changeViewModelStruct()
print("viewModel.data before fetch : \(viewModel.data)")
viewModel.fetchData
print("viewModel.data after fetch : \(self.viewModel.data)")
let viewModel = ViewModel()
var c = ViewController(viewModel: viewModel)
c.changeViewModelStruct()
控制台打印:
viewModel.data in didSet : 0
viewModel.data before fetch : 0
viewModel.data in fetchResponse : 10
viewModel.data after fetch : 10
Apple Document 是这样说的:
第一次初始化属性时不会调用 willSet 和 didSet 观察者。仅当属性的值在初始化上下文之外设置时才会调用它们。
【讨论】:
是的,我知道。但我更喜欢使用 didSet 观察者而不是使用 fetchData 函数进行委托或回调。所以我使用结构而不是类。如果没有办法使用带有异步变异功能的结构,我应该使用类。以上是关于Swift:ViewModel 应该是结构还是类?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 swift iOS 中使用 swift 高阶函数从本地 json 创建 ViewModel
谁应该在 Swift 上将 DisposeBag 保留在 MVVM(+controller) 中