移动应用开发架构设计

设计良好的应用架构是构建可维护、可扩展和高质量应用的基础。以下是应用开发架构设计的关键考虑因素和常见模式:

核心架构原则

  1. 业务点分离:将应用分为不同的责任层
  2. 单一职责:每个组件只做一件事
  3. 可测试性:设计应便于单元测试和集成测试
  4. 可扩展性:能够轻松添加新功能
  5. 可维护性:代码清晰、文档完善

常见架构模式

1. MVC (Model-View-Controller)
  • 模型:数据与业务逻辑
  • 视图:用户界面
  • 控制器:处理用户输入,协调模型与视图
2. MVP (Model-View-Presenter)
  • 比MVC更解耦的变体
  • Presenter取代Controller,处理所有业务逻辑
3. MVVM (Model-View-ViewModel)
  • 特别适合数据绑定框架(如Android Jetpack, SwiftUI-RxSwift)
  • ViewModel暴露数据流供View观察
    处理业务逻辑,提供数据绑定,数据驱动型 UI(通过 @Published、ObservableObject 等)。
    易于单元测试(ViewModel 不依赖 UIKit)。
4. Clean Architecture
  • 分层架构(实体、用例、接口适配器、框架/驱动)
  • 依赖规则:内层不依赖外层
  • 适合复杂业务逻辑的应用

VIPER

VIPER 是 iOS 开发中一种清晰分离职责的架构模式,适合中大型项目,尤其需要高可测试性和模块化的场景。它将代码分为 View(视图)、Interactor(业务逻辑)、Presenter(协调)、Entity(数据模型)、Router(路由) 五个部分。下面详细说明如何设计 VIPER 架构,并提供完整代码示例。

VIPER 核心组件与职责

组件: 对应的职责分层 职责
View 展示 UI,处理用户交互,将事件传递给 Presenter(被动视图,无业务逻辑)。
Interactor: ViewModel 处理核心业务逻辑(如网络请求、数据库操作),与 Entity 交互。
Presenter:Manager 接收 View 的事件,调用 Interactor;从 Interactor 获取结果后更新 View。
Entity:model 纯粹的数据模型(如结构体或类)。
Router 处理模块间导航(如页面跳转),依赖 UIViewController 的传递。

VIPER 设计步骤与代码示例

场景示例:实现一个用户列表页面(UserList 模块)
1. 定义模块协议(Contracts)

明确各组件间的通信接口,放在 UserListContracts.swift 中:

// MARK: - 模块协议
protocol UserListViewProtocol: AnyObject {
    func showUsers(_ users: [User])
    func showError(_ error: String)
}

protocol UserListPresenterProtocol {
    func viewDidLoad()
    func didSelectUser(_ user: User)
}

protocol UserListInteractorProtocol {
    func fetchUsers()
}

protocol UserListRouterProtocol {
    func navigateToUserDetail(_ user: User)
}

2. 实现 Entity

定义数据模型(简单结构体):

// User.swift
struct User {
    let id: Int
    let name: String
    let email: String
}

3. 实现 Interactor

处理数据获取逻辑(如网络请求):

// UserListInteractor.swift
class UserListInteractor: UserListInteractorProtocol {
    weak var presenter: UserListPresenterProtocol?
    
    func fetchUsers() {
        // 模拟网络请求
        let users = [
            User(id: 1, name: "Alice", email: "alice@example.com"),
            User(id: 2, name: "Bob", email: "bob@example.com")
        ]
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.presenter?.didFetchUsers(users)
        }
        
        // 错误示例
        // presenter?.didFailWithError("Network error")
    }
}

4. 实现 Presenter

协调 View 和 Interactor:

// UserListPresenter.swift
class UserListPresenter: UserListPresenterProtocol {
    weak var view: UserListViewProtocol?
    var interactor: UserListInteractorProtocol
    var router: UserListRouterProtocol
    
    init(view: UserListViewProtocol, interactor: UserListInteractorProtocol, router: UserListRouterProtocol) {
        self.view = view
        self.interactor = interactor
        self.router = router
    }
    
    func viewDidLoad() {
        interactor.fetchUsers()
    }
    
    func didSelectUser(_ user: User) {
        router.navigateToUserDetail(user)
    }
    
    // Interactor 回调方法
    func didFetchUsers(_ users: [User]) {
        view?.showUsers(users)
    }
    
    func didFailWithError(_ error: String) {
        view?.showError(error)
    }
}

5. 实现 View(ViewController)

被动展示数据,处理用户输入:

// UserListViewController.swift
class UserListViewController: UIViewController, UserListViewProtocol {
    var presenter: UserListPresenterProtocol!
    private var users: [User] = []
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        presenter.viewDidLoad()
    }
    
    // MARK: - UserListViewProtocol
    func showUsers(_ users: [User]) {
        self.users = users
        tableView.reloadData()
    }
    
    func showError(_ error: String) {
        let alert = UIAlertController(title: "Error", message: error, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

extension UserListViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        presenter.didSelectUser(users[indexPath.row])
    }
    
    // ... 其他 UITableView 方法
}

6. 实现 Router

处理页面跳转:

// UserListRouter.swift
class UserListRouter: UserListRouterProtocol {
    weak var viewController: UIViewController?
    
    static func createModule() -> UIViewController {
        let view = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "UserListViewController") as! UserListViewController
        let interactor = UserListInteractor()
        let router = UserListRouter()
        let presenter = UserListPresenter(view: view, interactor: interactor, router: router)
        
        view.presenter = presenter
        interactor.presenter = presenter
        router.viewController = view
        
        return view
    }
    
    func navigateToUserDetail(_ user: User) {
        let detailVC = UserDetailRouter.createModule(user: user)
        viewController?.navigationController?.pushViewController(detailVC, animated: true)
    }
}

7. 组装模块(依赖注入)

在入口处创建 VIPER 模块:

// AppDelegate 或 SceneDelegate
let userListVC = UserListRouter.createModule()
window?.rootViewController = UINavigationController(rootViewController: userListVC)

VIPER 关键设计原则

  1. 单向数据流
    View → Presenter → Interactor → Presenter → View

  2. 依赖规则

    • View 知道 Presenter,但不知道 Interactor 或 Router。
    • Presenter 知道 View 和 Interactor,但不知道 Router 的具体实现。
    • Router 知道如何创建模块和导航。
  3. 测试优势

    • View:测试 UI 展示是否正确(通过 Mock Presenter)。
    • Presenter:测试事件传递和逻辑协调(通过 Mock View 和 Interactor)。
    • Interactor:测试纯业务逻辑(不依赖 UIKit)。

VIPER 的优缺点

优点 缺点
职责清晰,模块化高 代码量显著增加(适合大型项目)
便于单元测试 学习曲线较陡
避免 Massive View Controller 需要手动管理模块间依赖

何时使用 VIPER?

  • 项目复杂度高,需要长期维护。
  • 团队协作开发,需严格分工。
  • 对测试覆盖率要求高(如金融、医疗类应用)。

扩展建议

  • 使用代码生成工具
    Generamba 自动生成 VIPER 模板文件。
  • 结合 Combine/RxSwift
    在 Presenter 中处理响应式数据流,简化回调逻辑。

通过 VIPER,你可以构建高度解耦、可测试的 iOS 应用,但需权衡其带来的额外代码成本。


现代应用架构组件:分层架构
  1. UI层

    • Activities/Fragments (Android)
    • ViewControllers/Views (iOS)
    • 组合式UI (Jetpack Compose/SwiftUI)
  2. 业务层

    • 业务逻辑和用例
    • 领域模型
  3. 数据层

    • 仓库模式(Repository)
    • 本地数据源(Room, CoreData)
    • 远程数据源(Retrofit, Alamofire)
  4. 依赖注入:测试、第三方功能

    • Dagger Hilt (Android)
    • Swinject (iOS) 是一个轻量级的依赖注入框架,专为 Swift 语言设计。
    • 减少耦合,提高可测试性
架构决策考虑因素
  1. 应用复杂度:简单应用可能不需要复杂架构
  2. 团队规模:大型团队需要更严格的架构
  3. 测试需求:高测试覆盖率需要更解耦的设计
  4. 平台特性:考虑原生平台的最佳实践
  5. 未来扩展:预留适当扩展空间
实施建议
  1. 从简单开始,随着应用增长逐步演进架构
  2. 保持一致性 - 整个应用使用相同架构模式
  3. 文档化架构决策和模式
  4. 定期重构以保持架构健康
  5. 考虑使用模板或脚手架工具保持一致性

App完善项:

卡顿分析:Firebase、友盟
内存泄漏:MLeaksFinder
异常处理:AvoidCrash、JJException,但AvoidCrash已经停止维护,使用AvoidCrash后期需要自己维护,如果要使用第三方库可以考虑集成JJException
修复文件:MangoFix 使用OC相似语法编写热修复文件,支持Swift AES128加/解密热修复确保安全,内置语法、词法分析使用OC的Runtime动态替换原方法 原方法前缀添加ORG保留

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容