SwiftUI 展示了一种新的、更快、更高效的视图构建方式。 声明式编程是一项了不起的技术,SwiftUI 以及 Android 上的 Jetpack Compose 和 Flutter 的 Widget 正在使 w 视图构建变得愉快和主动。 但是,构建视图并不是移动开发人员生活的唯一部分。 在瞬息万变的环境中设计一个良好的、可扩展的和有效的 UI 可以很好地与任何类型的模型配合使用是一个相当大的挑战。
一般是怎么处理的?
在最常见和最简单的情况下,有两个对象:
依赖关系图如下所示:
和代码:
struct UserListView: View { var viewModel: UserViewModel var body: some View { switch viewModel.state { case .loading: LoaderView() case .loaded(let users): List(users) { UserRow(user: $0) } case .failure(let error): Text(error.localzableDescriptioin) } }}struct UserRow: View { let user: User var body: some View { VStack(alignment: .leading) { Text("\(user.id): \(user.name)") Text(user.email) } }}
将模型直接注入视图是一个......有效的想法。 不要理解我的错误,我并不是说这是错误或错误的方法。 我相信当模型是原始类型时,这些解决方案运行良好,例如
将上述类型注入视图可以避免多余的样板代码,如额外的抽象层或仅为满足多余需求而创建的 ViewModel 类。 此外,当一个大视图被分成多个小视图时,更改特定的视图很容易,特别是当它们不依赖于模型,而是依赖于原始类型时。
第二个,也是非常流行的方法来自 MVVM 架构。 在这种架构中,大多数视图都有自己的 ViewModel。
MVVM 在模型和视图之间实施了一个额外的层,称为 ViewModel。 模型和视图对象与前面的例子没有什么不同,一个重要的区别是如何将可显示的数据传递给视图。
struct UserListView: View { @ObservedObject var viewModel: UserViewModel var body: some View { VStack { List(viewModel.elements) { UserRow(viewModel: $0) } } .onAppear { viewModel.fetchData() } }}class UserViewModel: ObservableObject { @Published var elements: [UserRowViewModel] = [] private let userRepository: UserRepository init(userRepository: UserRepository) { self.userRepository = userRepository } func fetchData() { elements = userRepository.fetch().map { UserRowViewModel(user: $0) } }}struct UserRow: View { let viewModel: UserRowViewModel var body: some View { VStack(alignment: .leading) { Text(viewModel.header) Text(viewModel.description) } }}struct UserRowViewModel: Identifiable { var id: String { user.id } var header: String { "\(user.id): \(user.name)" } var description: String { user.email } let user: User}
这通常是解决视图层和模型层之间的依赖问题的好方法。在开发人员中广为人知。这种方法的最大优点是
但我也看到了一些缺点:
出现了附加要求,在用户列表中,它不仅必须是用户,还必须是组,这是一个完全不同的模型。
知道了这些问题,我们可以顺利地继续:
面向协议的方法
我推荐介于 MVVM 和原始模型之间的东西——视图依赖。为了避免:
并确保:
协议方法的主要目标是创建一个易于适应和灵活的环境,以适应任何类型的业务需求。
它的关键部分是一个通常称为 DisplayableModel 的协议,它描述了应该在单个视图上确切显示的内容。
它可能看起来像这样
protocol UserRowDisplayableModel { var name: String { get } var avatar: ImageSource { get }}
可显示协议定义了视图所需的所有数据。 在这里,它是一个名称变量,描述每个用户行视图都有一个名称文本元素和图像源,以便在屏幕上显示用户头像。 可显示模型内部没有模型,适用于任何类型的业务逻辑。 其中的属性对于正确显示视图至关重要。 里面没有更多的处理。 DisplayableModel 的目的是尽可能的清晰和小巧。
视图接受 DisplayableModel 协议作为入口点。
struct UserRow: View { let model: UserRowDisplayableModel var body: some View { // Body }}
由 UserRow 处理的每个模型都必须实现 DisplayableModel 协议,例如
接下来,必须在 UserView 上显示的每个模型都必须实现 DisplayableModel 协议,例如
extension User: UserRowDisplayableModel { var name: String { "\(firstName), \(lastName)" } var avatar: ImageSource { ImageSource(url: avatarUrl) }}extension Group: UserRowDisplayableModel { var name: String { "\(groupName)" } var avatar: ImageSource { MixedAvatarImageSource(urls: users.map { $0.avatar }) }}
剩下的就看开发商了。 可显示的模型可以来自 ViewModel、observable store 或您希望的任何来源。
使用这种技术,您可以构建快速、可扩展且易于采用的视图。
视图模型示例
我想结合最著名的架构 MVVM 来展示它的外观。 此外,当您只下载一个模型时,我还展示了一个比普通案例复杂一点的商业案例示例。 要求与上述相同:
您不仅要显示用户,还要在同一个列表视图中显示组
class ViewModel: ObservableObject { @Published var elements: [UserRowDisplayableModel] = [] func fetch() { Task { async let users = await fetchUsers() // Return [User] async let group = await fetchGroups() // Return [Group] elements = await users + group } } /// Private methods}
结论
协议是迄今为止 Swift 拥有的最好的特性之一。 它允许编写一个简单、描述良好且清晰的代码,该代码具有一个且只有一个易于理解的目的。
我强烈建议所有开发人员将大部分代码封装到协议中,不仅因为团队中的其他工程师清晰易懂,而且更容易测试。