(二)IOS开发-MVC和MVVM设计架构

前言

MVC(Model-View-Controller)和MVVM(Model-View-ViewModel)都是IOS开发中常用的设计架构,它们的核心目的是为了分离关注点,增强代码的可维护性、可扩展性和复用性。尽管它们都遵循分层的设计思想,但两者之间存在一些明显的区别,尤其是在如何组织业务逻辑和UI层之间的关系上。

具体内容

1. MVC(Model-View-Controller)

概述:在MVC架构中,应用分为三部分:Model(模型)、View(视图)和Controller(控制器)。

  • Model:代表数据层,负责应用的核心业务逻辑和数据操作。它独立于视图和控制器,只关心数据的存储和处理。
  • View:负责呈现数据给用户,并处理用户的输入(如点击、滑动等)。它只是UI界面的呈现层,尽量不包含业务逻辑。
  • Controller:控制器作为中介,处理视图和模型之间的交互,接收用户输入并根据输入更新模型,或者更新视图的显示内容。

特点

  • 视图和控制器的耦合较高:在传统的iOS MVC架构中,控制器往往既管理视图的布局和展示,又承担业务逻辑,容易造成"臃肿的控制器"(Massive View Controller)问题。
  • 适用于简单应用:MVC架构适合数据交互和界面展示较为简单的应用。

优点

  • 简单易理解,适用于简单的应用程序。
  • 可以较好地分离UI层和数据层的关注点。

缺点

  • 随着应用规模的增长,Controller容易变得复杂和臃肿。
  • 视图和控制器之间的耦合较高,导致难以扩展和维护。

2. MVVM(Model-View-ViewModel)

概述:MVVM是对MVC的改进,特别适用于数据绑定的应用。它引入了一个额外的层次------ViewModel,用来协调视图和模型之间的关系。MVVM模式特别适合于需要复杂数据交互和UI更新的应用。

  • Model:与MVC中的Model相同,表示应用的核心数据和业务逻辑。它与视图和ViewModel无关。
  • View:负责展示数据,通常是用户界面的部分。它只关心如何展示信息,不包含逻辑。它通常与ViewModel绑定,以便在数据变化时自动更新UI。
  • ViewModel:ViewModel是View与Model之间的桥梁,负责从Model获取数据,并将这些数据转换为View能够展示的格式。它不直接操作视图,但通常会使用数据绑定来通知View更新。

特点

  • ViewModel作为视图与模型的中介:ViewModel处理与视图的交互逻辑,把Model的数据转换成View需要的格式,并负责维护视图的状态。
  • 数据绑定 :MVVM的关键特点之一是通过数据绑定机制(如使用RxSwiftCombine等库)实现视图和ViewModel之间的同步,减少手动更新UI的代码量。
  • 适用于复杂UI:当应用中需要处理大量数据和复杂的UI交互时,MVVM模式表现得尤为强大。

优点

  • 解耦了视图和模型:视图和模型不直接交互,通过ViewModel来进行桥接。View只关心如何展示数据,Model只关心数据逻辑。
  • 提高了可测试性:ViewModel通常不依赖于具体的UI,因此更容易进行单元测试。
  • 便于数据绑定:通过数据绑定,视图和ViewModel之间的数据同步变得非常高效和自动化。

缺点

  • 相对复杂:对于小型或简单的应用,MVVM可能显得过于复杂,增加了额外的抽象层。
  • 学习成本:对于初学者来说,MVVM可能比MVC更难理解,尤其是涉及到数据绑定和Reactive编程时。

MVC与MVVM的对比

特性 MVC MVVM
架构层次 3层:Model、View、Controller 3层:Model、View、ViewModel
View与Model的关系 View通过Controller与Model交互 View通过ViewModel与Model交互
控制逻辑位置 控制器包含大部分业务逻辑和UI更新 逻辑被分配到ViewModel,Controller相对较轻
数据绑定 无内建的数据绑定机制,视图需要手动更新 数据绑定使得视图与ViewModel保持同步(如RxSwift)
适用场景 简单应用、UI更新较少、逻辑不复杂的应用 复杂UI和大量数据交互的应用
视图与控制器耦合度 较高,控制器处理视图的显示和数据更新 较低,视图和ViewModel之间的耦合度更小
可测试性 测试较困难,尤其是控制器的单元测试 更容易测试,ViewModel可以独立于UI进行单元测试
复杂度 适用于简单应用,复杂度低 适用于复杂应用,复杂度较高

示例:

我们要展示一个用户列表,其中每个用户有姓名和邮箱。应用会从网络上获取这些数据,并通过MVVM架构来管理数据和UI。

步骤 1:定义模型(Model)

模型表示应用的核心数据,我们定义一个User模型来表示每个用户。

rust 复制代码
// Model
struct User {
    let name: String
    let email: String
}

步骤 2:定义视图模型(ViewModel)

视图模型负责从模型中获取数据,并将数据转换为视图所需的格式。它还负责管理业务逻辑,例如处理加载数据的状态。

swift 复制代码
import Foundation
import RxSwift
import RxCocoa

// ViewModel
class UserListViewModel {

    private let disposeBag = DisposeBag()

    // Observable properties for the view to bind to
    var users: BehaviorRelay<[User]> = BehaviorRelay(value: [])
    var isLoading: BehaviorRelay<Bool> = BehaviorRelay(value: false)
    var errorMessage: BehaviorRelay<String?> = BehaviorRelay(value: nil)

    // Fetch data (模拟网络请求)
    func fetchUsers() {
        isLoading.accept(true)
        // 模拟网络请求,1秒后返回数据
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            let sampleUsers = [
                User(name: "John Doe", email: "john.doe@example.com"),
                User(name: "Jane Smith", email: "jane.smith@example.com")
            ]

            // 在主线程更新数据
            DispatchQueue.main.async {
                self.isLoading.accept(false)
                self.users.accept(sampleUsers)
            }
        }
    }
}

步骤 3:定义视图(View)

视图负责展示数据并响应用户交互。在这里,我们使用一个UIViewController来表示视图,它会从视图模型中绑定数据并展示给用户。

swift 复制代码
import UIKit
import RxSwift
import RxCocoa

// View
class UserListViewController: UIViewController {
    
    private let disposeBag = DisposeBag()
    private let viewModel = UserListViewModel()
    
    // UI Elements
    private let tableView = UITableView()
    private let loadingIndicator = UIActivityIndicatorView(style: .large)
    private let errorLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupUI()
        bindViewModel()
        
        // Fetch users data
        viewModel.fetchUsers()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        view.addSubview(loadingIndicator)
        view.addSubview(errorLabel)
        
        // Layout your UI components here (simplified for example)
        tableView.frame = view.bounds
        loadingIndicator.center = view.center
        errorLabel.frame = CGRect(x: 20, y: 100, width: view.frame.width - 40, height: 50)
        
        errorLabel.textColor = .red
        errorLabel.isHidden = true
        
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
    }
    
    private func bindViewModel() {
        // Binding the users data from ViewModel to the tableView
        viewModel.users
            .bind(to: tableView.rx.items(cellIdentifier: "UserCell")) { (index, user, cell) in
                cell.textLabel?.text = "(user.name) - (user.email)"
            }
            .disposed(by: disposeBag)
        
        // Binding the loading state to the loading indicator
        viewModel.isLoading
            .subscribe(onNext: { [weak self] isLoading in
                self?.loadingIndicator.isHidden = !isLoading
                if isLoading {
                    self?.loadingIndicator.startAnimating()
                } else {
                    self?.loadingIndicator.stopAnimating()
                }
            })
            .disposed(by: disposeBag)
        
        // Binding the error message
        viewModel.errorMessage
            .subscribe(onNext: { [weak self] errorMessage in
                self?.errorLabel.text = errorMessage
                self?.errorLabel.isHidden = errorMessage == nil
            })
            .disposed(by: disposeBag)
    }
}

步骤 4:运行应用

在应用启动时,UserListViewController会请求数据并通过UserListViewModel来获取数据。视图模型通过BehaviorRelay将数据传递给视图,并通过RxSwift将数据绑定到UI组件(如UITableView)。

代码解释

  • ModelUser结构体表示用户数据。
  • ViewModelUserListViewModel管理应用的逻辑。它负责获取用户数据(模拟网络请求),并通过BehaviorRelay通知视图更新数据。BehaviorRelay是RxSwift的一种类型,它能提供实时的可观察数据流。
  • ViewUserListViewController展示用户列表,并通过RxSwift的数据绑定来实时更新UI。它会绑定ViewModel中的数据,并在数据发生变化时自动更新UI。

我们在这个例子中使用了RxSwift来实现数据绑定和响应式编程。视图模型通过BehaviorRelay来提供数据流,视图通过订阅这些数据流来自动更新UI。RxSwift使得视图和视图模型之间的交互变得更加简单和高效,避免了直接的回调和手动更新UI的操作。

总结

  • MVC:适合小型应用或简单的界面,易于理解和实现,但容易导致控制器过于臃肿,且难以扩展。适合数据交互较简单的应用。
  • MVVM:适合复杂的应用,尤其是在需要大量数据绑定和UI交互时。它将视图逻辑和业务逻辑分离,使得应用更加模块化,易于维护和扩展,但相对复杂,需要更多的学习成本。

两种模式各有优劣,选择哪种架构取决于应用的复杂度、开发团队的经验以及开发流程的需求。

相关推荐
哑巴语天雨6 小时前
React+Vite项目框架
前端·react.js·前端框架
码农老起6 小时前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
恋猫de小郭7 小时前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
撸码到无法自拔10 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript
Domain-zhuo11 小时前
如何利用webpack来优化前端性能?
前端·webpack·前端框架·node.js·ecmascript
网安墨雨11 小时前
iOS应用网络安全之HTTPS
web安全·ios·https
福大大架构师每日一题12 小时前
37.1 prometheus管理接口源码讲解
ios·iphone·prometheus
web1508509664121 小时前
【React&前端】大屏适配解决方案&从框架结构到实现(超详细)(附代码)
前端·react.js·前端框架
Cachel wood1 天前
Vue.js前端框架教程8:Vue消息提示ElMessage和ElMessageBox
linux·前端·javascript·vue.js·前端框架·ecmascript
Super毛毛穗1 天前
Vue中<script setup></script>的主要语法元素和特性
前端·javascript·vue.js·前端框架