本人对 MVVM 不熟,看了资料只知道概念并没有透彻的理解。
按我之前学习的理解,一个 ObservableObject
是一个 ViewModel
,发布订阅的是 State
(网上讲的都是这种),但是当 State
里面的数据也需要双向绑定的时候(传给子 View
),发布订阅就变成了另外的 ViewModel
,这里面是不是有啥问题。比如下面 MainViewModel
,我是不是应该发布的是 users
而不是 userViewModels
class MainViewModel: ObservableObject {
@Published var userViewModels: [UserViewModel] = []
}
class UserViewModel: Identifiable, ObservableObject {
@Published var name = ""
let id = UUID()
}
1
cardioio 2022-06-03 11:42:10 +08:00 1
|
2
weiwoxinyou 2022-06-03 12:06:33 +08:00 via Android
在 react 里面要实现这个变化是通过 props, 每个组件只维护自身的状态,来自上级的状态应该交给上级自己维护
|
3
MakHoCheung OP @cardioio 我疑问的是如果要改 user 的 name 这种情况呢,User 就只能是 Class 了,这个时候算不算是 UserViewModel
|
4
agagega 2022-06-03 12:11:13 +08:00
我没能太理解你的问题。
@ ObservedObject 和 @ State 标记都是用来做数据绑定的,View 里如果引用到了这些标记的变量,当它们改变时,View 也会对应更新。 之所以要区分 ObservedObject 和 State ,是因为 Swift 不同于 JS ,是一个严格区分值语义和对象语义的语言。Swift 中,struct 和大部分没有 NS 前缀的内置类型都是值语义,即任何一部分被修改了都会视为整个对象被修改;而 class 和 Foundation 里 NS 开头的类型都是对象语义,对它们属性的修改并不会被视为对整个对象的修改。 因为 MVVM 的核心就是追踪绑定数据的改变,所以 SwiftUI 必须区别对待值语义变量和对象语义变量。对值语义用 @ State ,任何修改都会简单触发 View 重新渲染;对对象语义用 @ ObservedObject ,被它修饰的类型都要满足 ObservableObject 这个协议,这些类型中任何被 @ Published 修饰的成员发生修改,整个对象就会触发 objectWillChange 事件。 这和 ViewModel 本身没有关系,只是因为 ViewModel 本来就是我们自己定义出来封装事件逻辑的模块,所以通常会实现为 class ,然后加上 @ ObservedObject 做修饰。 SwiftUI 里还有个修饰符叫 @ StateObject ,也是修饰对象语义的,和 ObservedObject 的区别在于后者不会维护对象的生命周期,而 StateObject 在生命周期上和 State 类似,由当前组件来维护。 |
5
MakHoCheung OP |
6
cardioio 2022-06-03 12:20:05 +08:00
@MakHoCheung 要改 name ,很简单啊,在 UserViewModel class 里定义方法就行了。调用的时候在相应的 view 声明实例化就行了。
|
7
MakHoCheung OP @cardioio 是的,这个时候问题就来了,现在没有了 Model 了。UserViewModel 承担了 ViewModel 和 Model 的角色,这么做感觉不是 MVVM 了
|
8
agagega 2022-06-03 12:34:23 +08:00
@MakHoCheung
SwiftUI 有 @ Binding 这个东西,如果你的 User 是 struct 并且 user 是 @ State ,那可以直接$user 传给子组件,子组件 @ Binding user: User 。这种情况因为都是值语义所以不需要定义 ViewModel 了 |
9
agagega 2022-06-03 12:35:52 +08:00
@agagega
复杂的情况下你可以自己定义 Binding ,@ Binding 和$只是语法糖,Binding 的本质就是一个封装了 getter 和 setter 的对象: https://developer.apple.com/documentation/swiftui/binding |
10
jackyin 2022-06-03 13:13:47 +08:00
@MakHoCheung
首先,我想说句正确的废话,你之所以没有 model ,是因为你没有定义 Model 。 你直接把 User 的 name 属性放到了 ViewModel 里了,你这样做有个问题就是当你只想使用 Model 而不是 ViewModel 的时候怎么办? 所以,你为什么不先定义一个 User 作为 Model ,再把这个 User 作为一个 @Published 属性放到 UserViewModel 里呢? 另外,要灵活,我记得笑傲江湖里令狐冲被田伯光打掉了剑,于是不能用剑刺了,风清扬提醒他为什么刺剑一定要用剑呢,手指也可以作为剑。回到你的疑问里,你为什么一定纠结于 Model 一定要是个 struct 呢,简单的 Int 或者 String 也可以视作 Model 来使用,而且很常见,比如可以定义一些全局的参数放到一个单例 ViewModel 里,就很方便使用。 |
11
hguandl 2022-06-03 13:44:33 +08:00
不知道 OP 是否看了 WWDC19 的演讲“通过 SwiftUI 的数据流”,这个是最初 SwiftUI 发布时苹果官方对于数据流的介绍。如果没有看过建议补一下 https://developer.apple.com/wwdc19/226 。苹果 WWDC 里的演讲虽然代码不多,但是概念讲解很生动。我认为这应该是学习 Swift 时选择的第一手资料,然后再去 hackingwithswift 等地方学习有经验开发者总结的教程。
上面链接里的教程由于是最早期的版本,个别 API 存在一些变动。比如里面提到的 BindableObject 已经更名为 ObservedObject ,@Published 属性引入后也不需要像视频里那样手动写更新了。 Apple Developer 里的学习资料很多,而且近几年的演讲视频都配了中文字幕很不错。不过因为一些术语也翻译成了中文,搜索起来有点麻烦。 |
12
wooi 2022-06-03 15:12:47 +08:00
你的 User 或者 Name 都是 Model ,ViewModel 承载着 Model 计算逻辑,例如显示名字 View 要订阅 Name View 点击触发 ViewModel 内的计算逻辑,逻辑执行完毕之后更新被观察到属性,一旦 Name 值发生变化自动刷新你名字 View
|
13
MakHoCheung OP @jackyin
// View struct UsersView: View { @StateObject var usersViewModel = UsersViewModel() var body: some View { VStack { ForEach(usersViewModel.users) { UserView(user: $0) } } .onAppear { usersViewModel.initUsers() } } } struct UserView: View { @StateObject var user: User var body: some View { VStack { HStack { Text(user.name) Button("改名") { user.changeName(newName: "小明") } } } } } // ViewModel class UsersViewModel: ObservableObject { @Published var users = [User]() func initUsers() { users.append(User()) users.append(User()) users.append(User()) } } // Model class User: ObservableObject, Identifiable { @Published private(set) var name = "待改名" let id = UUID() func changeName(newName: String) { name = newName } } 我这个算 MVVM 吗。我这里把 User 定位为 Model 是对的吗 |
14
hocgin 2022-06-03 20:52:22 +08:00 via iPhone
User 应该是纯粹的数据结构
|
15
jackyin 2022-06-03 21:56:06 +08:00
@MakHoCheung
可以的,就像 14 楼说的,Model 其实就是数据结构,所以 User 可以看作是 Model ,然后你继承了 ObservableObject ,User 就又可以视作 ViewModel 了。即 User 既为 Model ,又为 ViewModel 。 另外,我觉得 UsersViewModel 是非必要的,直接在 UsersView 里定义 @State var users = [User]()即可。 我也是新学的 SwiftUI ,我是做 php 的哈哈哈,昨天刚上架一个背单词的 app ,叫今日背单词,也是 SwiftUI 做的,后台 api 用的 golang ,一起探讨学习吧,好多效果我也还实现不了。 https://apps.apple.com/cn/app/今日背单词 /id1619751017 ------------------------------------------------------------ import SwiftUI struct UsersView: View { @State var users = [User]() var body: some View { VStack { ForEach(users) { UserView(user: $0) } } .onAppear { users.append(User()) users.append(User()) users.append(User()) } } } struct UserView: View { @StateObject var user: User var body: some View { VStack { HStack { Text(user.name) Button("改名") { user.changeName(newName: "小明") } } } } } // Model class User: ObservableObject, Identifiable { @Published private(set) var name = "待改名" let id = UUID() func changeName(newName: String) { name = newName } } |