我正在尝试在我的新项目中使用MVVM模式。第一次,我创建了我的所有视图模型到struct。但是当我使用闭包实现异步业务逻辑(如fetchDataFromNetwork)时,闭包会捕获旧的视图模型值,然后更新到那个。不是新的视图模型值。
这是操场上的测试代码。
import Foundation
import XCPlayground
struct ViewModel {
var data: Int = 0
mutating func fetchData(completion:()->()) {
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.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.
答案 0 :(得分:7)
你应该使用一个班级。
如果使用具有变异函数的结构,则该函数不应在闭包内执行变异;您应不执行以下操作:
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://stackoverflow.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()) {...}
。
答案 1 :(得分:1)
您可以在两个选项中获取self.data
:在fetchResponse
的闭包中使用return参数(使用viewModel
作为struct
)或者您可以创建自己的init
set-method / closure并在viewModel
方法中使用它(使用class
作为class ViewModel {
var data: Int = 0
func fetchData(completion:()->()) {
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.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
控制台打印:
csv = r.text
outfile = open('/path/to/my.csv', 'w')
outfile.write(csv)
outfile.close()
Apple Document 像这样说:
首次初始化属性时,不会调用willSet和didSet观察者。仅在属性的值设置在初始化上下文之外时才调用它们。