MVP(Model-View-Presenter) 是一种常见的 软件架构模式,用于分离应用程序的 UI 逻辑与业务逻辑,它类似于 MVC(Model-View-Controller),但与 MVC 相比,MVP 更加强调了 Presenter 作为中介者的角色,进一步解耦了视图与数据的处理逻辑。

在 iOS 开发 中使用 MVP 架构模式 时,主要有三个核心组成部分:

  1. Model(模型层):负责应用程序的数据和业务逻辑,通常会包含网络请求、数据库操作、模型数据等内容。
  2. View(视图层):负责展示界面和与用户的交互。它不包含任何业务逻辑,而是通过调用 Presenter 来更新视图。
  3. Presenter(展示层):处理用户交互逻辑,它是连接 View 和 Model 的桥梁。Presenter 通过请求 Model获取数据,并通过 View 更新 UI。

MVP 与 MVC 的区别

  • MVC 中,Controller 既负责用户交互,也负责更新视图。它充当了视图和模型之间的中介,但通常会导致控制器变得过于臃肿。
  • MVP 中,Presenter 专注于业务逻辑和用户交互,View 只负责展示和用户交互,而 Presenter 执行大部分的业务逻辑。

MVP 架构的三个主要角色

1. Model(模型层)

  • 负责持久化数据、业务逻辑、与网络接口交互等。
  • 通常是数据模型和服务层的集合。
  • 例如:网络请求、数据存储等。
struct User {
    var name: String
    var age: Int
}

class UserService {
    func fetchUserData(completion: @escaping (User) -> Void) {
        // 假设这是从服务器获取数据
        let user = User(name: "John", age: 25)
        completion(user)
    }
}

2. View(视图层)

  • 负责所有与 UI 相关的操作,如显示数据、接收用户输入。
  • View 只负责展示,不处理任何业务逻辑。
  • View 通过协议向 Presenter 通知用户的交互(例如按钮点击、文本输入等)。
protocol UserView: AnyObject {
    func displayUserData(user: User)
}

class UserViewController: UIViewController, UserView {
    var presenter: UserPresenter!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter = UserPresenter(view: self)
        presenter.loadUserData()
    }
    
    func displayUserData(user: User) {
        // 更新 UI,显示用户数据
        print("User name: \(user.name), age: \(user.age)")
    }
}

3. Presenter(展示层)

  • 负责从 Model 获取数据,并更新 View
  • Presenter 持有对 View 的引用,并可以调用 View 上的方法来更新 UI。
  • 它充当了中介的角色,负责决定如何处理数据并与 View 进行交互。
protocol UserPresenterProtocol {
    func loadUserData()
}

class UserPresenter: UserPresenterProtocol {
    weak var view: UserView?
    var userService: UserService

    init(view: UserView) {
        self.view = view
        self.userService = UserService()
    }

    func loadUserData() {
        userService.fetchUserData { [weak self] user in
            self?.view?.displayUserData(user: user)
        }
    }
}

MVP 架构的流程

  1. View 初始化时,创建 Presenter 并调用 Presenter 方法来加载数据。
  2. Presenter 通过调用 Model 层来获取数据(如网络请求、数据库访问等)。
  3. 数据获取后,Presenter 更新 View,让 View 展示新的数据。

主要优点

  1. 解耦性高:业务逻辑(Presenter)与 UI 逻辑(View)分开,易于维护和扩展。
  2. 单元测试友好:由于 Presenter 完全不依赖于 UI,可以方便地对其进行单元测试,而不需要依赖于界面。
  3. 可重用性:由于 Presenter 和 View 解耦,你可以轻松地为不同平台(iPhone, iPad)或者不同 UI 层次(例如:Modal vs. Normal View)重用 Presenter 层。

主要缺点

  1. Presenter 增加了复杂度:尽管 Presenter 提供了更多的解耦,但会使得架构相对更加复杂。需要更多的类来管理界面逻辑。
  2. 需要更多的协议与代码结构:每个 View 都需要一个对应的协议,增加了额外的抽象层级和代码复杂度。

适用场景

  • 较为复杂的应用:如果你的应用包含多个业务逻辑层,并且需要与后端进行复杂的交互,使用 MVP 可以帮助保持代码的整洁。
  • 单元测试要求高的应用:由于 Presenter 与 View 解耦,可以很方便地为 Presenter 写单元测试。

完整的示例

以下是一个完整的 MVP 架构示例,它展示了如何在 iOS 中实现这种架构。

// Model
struct User {
    var name: String
    var age: Int
}

class UserService {
    func fetchUserData(completion: @escaping (User) -> Void) {
        // 模拟网络请求
        let user = User(name: "Alice", age: 30)
        completion(user)
    }
}

// View
protocol UserView: AnyObject {
    func displayUserData(user: User)
}

class UserViewController: UIViewController, UserView {
    
    var presenter: UserPresenterProtocol!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        presenter = UserPresenter(view: self)
        presenter.loadUserData()
    }
    
    func displayUserData(user: User) {
        print("User Name: \(user.name), Age: \(user.age)")
    }
}

// Presenter
protocol UserPresenterProtocol {
    func loadUserData()
}

class UserPresenter: UserPresenterProtocol {
    weak var view: UserView?
    var userService: UserService
    
    init(view: UserView) {
        self.view = view
        self.userService = UserService()
    }
    
    func loadUserData() {
        userService.fetchUserData { [weak self] user in
            self?.view?.displayUserData(user: user)
        }
    }
}

小结

MVP(Model-View-Presenter) 架构模式通过 Presenter 充当中介者,将 View 和 Model 之间的逻辑分离开来。它为开发者提供了更清晰的代码结构,更高的可维护性和更易于测试的代码。然而,相比于 MVC,MVP 增加了额外的抽象和复杂度,适用于需要高度解耦和清晰的业务逻辑的应用场景。