swiftui 展示了一种新的、更快、更高效的视图构建方式。 声明式编程是一项了不起的技术,SwiftUI 以及 Android 上的 Jetpack Compose 和 Flutter 的 Widget 正在使 w 视图构建变得愉快和主动。 但是,构建视图并不是移动开发人员生活的唯一部分。 在瞬息万变的环境中设计一个良好的、可扩展的和有效的 UI 可以很好地与任何类型的模型配合使用是一个相当大的挑战。
一般是怎么处理的?
在最常见和最简单的情况下,有两个对象:
- 模型:从存储库中获取,可以是来自 API 的数据、来自数据库的记录或来自多种服务的其他类型的数据。 模型通常公开公开字段,很少公开方法。
- view:实现 View 协议的结构,在最好的情况下只有一个依赖。
依赖关系图如下所示:
和代码:
structUserListView:View{varviewModel:UserViewModelvarbody: someView{switchviewModel.state {case.loading:LoaderView()case.loaded(letusers):List(users) {UserRow(user: $0)
}case.failure(leterror):Text(error.localzableDescriptioin)
}
}
}structUserRow:View{letuser:Uservarbody: someView{VStack(alignment: .leading) {Text("\(user.id): \(user.name)")Text(user.email)
}
}
}
将模型直接注入视图是一个……有效的想法。 不要理解我的错误,我并不是说这是错误或错误的方法。 我相信当模型是原始类型时,这些解决方案运行良好,例如
- 细绳
- 布尔值
- 数字
将上述类型注入视图可以避免多余的样板代码,如额外的抽象层或仅为满足多余需求而创建的 ViewModel 类。 此外,当一个大视图被分成多个小视图时,更改特定的视图很容易,特别是当它们不依赖于模型,而是依赖于原始类型时。
第二个,也是非常流行的方法来自 MVVM 架构。 在这种架构中,大多数视图都有自己的 ViewModel。
MVVM 在模型和视图之间实施了一个额外的层,称为 ViewModel。 模型和视图对象与前面的例子没有什么不同,一个重要的区别是如何将可显示的数据传递给视图。
structUserListView:View{
@ObservedObjectvarviewModel:UserViewModelvarbody: someView{VStack{List(viewModel.elements) {UserRow(viewModel: $0)
}
}
.onAppear {
viewModel.fetchData()
}
}
}classUserViewModel:ObservableObject{
@Publishedvarelements: [UserRowViewModel] = []privateletuserRepository:UserRepositoryinit(userRepository:UserRepository) {self.userRepository = userRepository
}funcfetchData(){
elements = userRepository.fetch().map{UserRowViewModel(user: $0) }
}
}structUserRow:View{letviewModel:UserRowViewModelvarbody: someView{VStack(alignment: .leading) {Text(viewModel.header)Text(viewModel.description)
}
}
}structUserRowViewModel:Identifiable{varid:String{
user.id
}varheader:String{"\(user.id): \(user.name)"}vardescription:String{
user.email
}letuser:User}
这通常是解决视图层和模型层之间的依赖问题的好方法。在开发人员中广为人知。这种方法的最大优点是
- 层分离:抽象负责什么一目了然,领域模型和UI没有依赖关系
- 可测试性:视图模型通常很容易测试
- 视图模型通信:发布者或委托可以从父视图模型传递到子视图模型并在上层处理
但我也看到了一些缺点:
- 不同类型的来源:ViewModel 只接受一个模型。意味着一个类不可扩展,如果出现新的业务案例,则必须重写它。例如。
出现了附加要求,在用户列表中,它不仅必须是用户,还必须是组,这是一个完全不同的模型。
- 样板代码和 ViewModel 实际上只是模型的包装。除了公开一个字段以供查看外,别无他法。
- 在大多数情况下,为每个视图创建一个新的视图模型似乎是一种过度工程
知道了这些问题,我们可以顺利地继续:
面向协议的方法
我推荐介于 MVVM 和原始模型之间的东西——视图依赖。为了避免:
- 很多视图模型文件,通常只为私有 MVVM 架构而创建,什么都不做,只描述如何将模型转换为视图。
并确保:
- 良好的可测试性水平
- 轻松采用不同类型的资源
- 在需要时轻松从协议转换为视图模型
协议方法的主要目标是创建一个易于适应和灵活的环境,以适应任何类型的业务需求。
它的关键部分是一个通常称为 DisplayableModel 的协议,它描述了应该在单个视图上确切显示的内容。
它可能看起来像这样
protocolUserRowDisplayableModel{varname:String{get}varavatar:ImageSource{get}
}
可显示协议定义了视图所需的所有数据。 在这里,它是一个名称变量,描述每个用户行视图都有一个名称文本元素和图像源,以便在屏幕上显示用户头像。 可显示模型内部没有模型,适用于任何类型的业务逻辑。 其中的属性对于正确显示视图至关重要。 里面没有更多的处理。 DisplayableModel 的目的是尽可能的清晰和小巧。
视图接受 DisplayableModel 协议作为入口点。
structUserRow:View{letmodel:UserRowDisplayableModelvarbody: someView{// Body}
}
由 UserRow 处理的每个模型都必须实现 DisplayableModel 协议,例如
接下来,必须在 UserView 上显示的每个模型都必须实现 DisplayableModel 协议,例如
extensionUser:UserRowDisplayableModel{varname:String{"\(firstName), \(lastName)"}varavatar:ImageSource{ImageSource(url: avatarUrl)
}
}extensionGroup:UserRowDisplayableModel{varname:String{"\(groupName)"}varavatar:ImageSource{MixedAvatarImageSource(urls: users.map{ $0.avatar })
}
}
剩下的就看开发商了。 可显示的模型可以来自 ViewModel、observable store 或您希望的任何来源。
使用这种技术,您可以构建快速、可扩展且易于采用的视图。
视图模型示例
我想结合最著名的架构 MVVM 来展示它的外观。 此外,当您只下载一个模型时,我还展示了一个比普通案例复杂一点的商业案例示例。 要求与上述相同:
您不仅要显示用户,还要在同一个列表视图中显示组
classViewModel:ObservableObject{
@Publishedvarelements: [UserRowDisplayableModel] = []funcfetch(){
Task {asyncletusers =awaitfetchUsers()// Return [User]asyncletgroup=awaitfetchGroups()// Return [Group]elements =awaitusers +group}
}///Private methods}
- 可显示模型存储在数组中并标记为已发布
- 当视图出现时,或者当它需要时,方法 fetch 被调用
- 在其中,ViewModel 异步获取两个不同的模型:Group 和 User
- 因为两者都实现了 UserRowDisplayableModel 它可以很容易地传递给已发布的元素。
结论
协议是迄今为止 Swift 拥有的最好的特性之一。 它允许编写一个简单、描述良好且清晰的代码,该代码具有一个且只有一个易于理解的目的。
我强烈建议所有开发人员将大部分代码封装到协议中,不仅因为团队中的其他工程师清晰易懂,而且更容易测试。
评论0