iOS代码架构

iOS 代码架构的核心目标是 解耦、可维护、可测试,避免代码写成 “大泥球”(Massive ViewController)。主流架构均围绕 “职责拆分” 设计,以下是 iOS 开发中最常用、最实用的 4 种架构,用大白话 + 代码示例 + 适用场景说明,方便直接落地:

一、先搞懂:架构的核心是 “拆分职责”
iOS 界面相关代码的核心角色通常有 3 个:
数据层:获取 / 处理数据(比如请求接口、读数据库);
业务层:逻辑处理(比如数据转换、判断用户行为);
展示层:显示 UI、响应用户操作(比如 ViewController、View);
架构本质就是把这 3 个角色分开,避免所有代码都堆在 ViewController 里。
二、4 种主流架构(从简单到复杂)

  1. MVC(Model-View-Controller)—— 基础款,官方推荐
    iOS 原生支持的架构,最经典也最常用,入门必备,核心是 “三层分离”:
    Model(数据模型):只管数据,不关心 UI 和逻辑(比如 User 类、接口请求工具);
    View(视图):只管显示,不处理业务(比如自定义 Button、TableViewCell);
    Controller(控制器):中间枢纽,连接 Model 和 View(接收用户操作→调用 Model 处理→更新 View)。
    代码示例(Swift)
    swift
    // 1. Model:只存数据+数据处理
    struct UserModel {
    let name: String
    let age: Int
    // 数据转换(比如接口 JSON 转模型)
    static func fromJSON(_ json: [String: Any]) -> UserModel {
    return UserModel(
    name: json["name"] as? String ?? "",
    age: json["age"] as? Int ?? 0
    )
    }
    }

// 2. View:只管 UI 显示,不写逻辑
class UserView: UIView {
let nameLabel = UILabel()
let ageLabel = UILabel()
// 提供方法让外部设置数据(不主动获取数据)
func setUser(_ user: UserModel) {
nameLabel.text = "姓名:(user.name)"
ageLabel.text = "年龄:(user.age)"
}
}

// 3. Controller:连接 Model 和 View
class UserViewController: UIViewController {
private let userView = UserView()
private let userModel: UserModel

init(userJSON: [String: Any]) {
    self.userModel = UserModel.fromJSON(userJSON)
    super.init(nibName: nil, bundle: nil)
}

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(userView)
    // 控制器调用 View 的方法更新 UI
    userView.setUser(userModel)
}

}
适用场景 & 优缺点
✅ 适用:小型 App、快速迭代项目、新手入门(原生支持,无需额外配置);
✅ 优点:简单易懂、开发速度快、iOS 生态适配好;
❌ 缺点:Controller 容易臃肿(业务逻辑 + UI 调度全堆这),复杂项目难维护。
2. MVVM(Model-View-ViewModel)—— 进阶款,解决 MVC 臃肿
在 MVC 基础上新增 ViewModel,核心是 “ViewModel 接管业务逻辑”,让 Controller 只负责 UI 调度:
Model:和 MVC 一样,负责数据;
View:显示 UI(ViewController 也算 View 层,因为它负责 UI 调度);
ViewModel:核心!处理所有业务逻辑(数据转换、接口请求、状态管理),通过 “数据绑定” 给 View 传数据;
关键:View 和 ViewModel 不直接依赖,通过 绑定(Binding) 通信(iOS 常用 Combine 或第三方库 RxSwift 实现)。
代码示例(Swift + Combine)
swift
import Combine

// 1. Model:不变(数据层)
struct UserModel {
let name: String
let age: Int
static func fromJSON(_ json: [String: Any]) -> UserModel { ... }
}

// 2. ViewModel:接管业务逻辑,提供“可观察数据”
class UserViewModel {
// 可观察属性(View 监听这个属性,数据变了自动更新)
@Published var user: UserModel?
@Published var isLoading = false

// 业务逻辑:请求用户数据(模拟接口请求)
func fetchUser() {
    isLoading = true
    // 模拟网络请求(2 秒后返回数据)
    DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
        let json: [String: Any] = ["name": "张三", "age": 25]
        let user = UserModel.fromJSON(json)
        DispatchQueue.main.async {
            self.user = user
            self.isLoading = false
        }
    }
}

}

// 3. View(ViewController):监听 ViewModel,更新 UI
class UserViewController: UIViewController {
private let userView = UserView()
private let viewModel = UserViewModel()
// 用于取消 Combine 订阅(避免内存泄漏)
private var cancellables = Set()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(userView)
    
    // 绑定 ViewModel 的数据到 View
    viewModel.$user
        .compactMap { $0 } // 过滤 nil
        .sink { [weak self] user in
            self?.userView.setUser(user) // 数据变了,自动更新 UI
        }
        .store(in: &cancellables)
    
    // 绑定加载状态
    viewModel.$isLoading
        .sink { [weak self] isLoading in
            self?.userView.showLoading(isLoading)
        }
        .store(in: &cancellables)
    
    // 触发请求
    viewModel.fetchUser()
}

}
适用场景 & 优缺点
✅ 适用:中大型 App、复杂业务场景(比如电商、社交)、需要频繁更新 UI;
✅ 优点:Controller 极简洁(只做绑定)、业务逻辑可单独测试、解耦彻底;
❌ 缺点:需要学习 Combine/RxSwift、简单项目有点 “重”。
3. Clean Architecture(整洁架构)—— 高阶款,极致解耦
核心是 “依赖倒置”:外层依赖内层,内层不依赖任何外层(比如数据层不关心是 UI 还是测试调用它),分为 4 层(从内到外):
实体层(Entities):核心业务模型(比如 User,只定义业务属性,和任何框架无关);
用例层(Use Cases):业务逻辑(比如 “获取用户信息”“修改用户昵称”,调用实体层和数据层);
接口适配层(Interface Adapters):转换数据格式(比如把网络请求的 JSON 转成实体层的 User,把实体层的 User 转成 UI 用的 ViewModel);
框架层(Frameworks & Drivers):外部依赖(比如 UIKit、网络库、数据库,依赖接口适配层,不直接依赖内层)。
核心特点
每层只和内层通信(外层依赖内层的接口,不是具体实现);
任何一层都可以替换(比如把网络库从 AFNetworking 换成 Alamofire,不用改内层逻辑);
可测试性极强(内层逻辑可以脱离 UI 和网络单独测试)。
适用场景 & 优缺点
✅ 适用:大型 App、团队协作项目、长期维护的项目(比如企业级应用);
✅ 优点:极致解耦、可维护性拉满、可测试性强;
❌ 缺点:架构复杂、开发成本高、小项目没必要。
4. VIPER(View-Interactor-Presenter-Entity-Router)—— 组件化架构
把 Clean Architecture 落地成具体的 5 个角色,适合组件化、模块化开发:
View:显示 UI,响应用户操作(比如 ViewController);
Interactor:业务逻辑 + 数据获取(比如请求接口、处理数据,对应 Clean 的用例层);
Presenter:中间枢纽(接收 View 的请求→调用 Interactor→把数据转换成 View 能显示的格式→通知 View 更新);
Entity:数据模型(和 Clean 的实体层一致);
Router:路由(页面跳转、组件通信,比如从 A 页面跳转到 B 页面)。
适用场景 & 优缺点
✅ 适用:大型组件化项目、多团队协作(比如每个组件一个 VIPER 结构);
✅ 优点:职责划分极细、组件独立、便于分工;
❌ 缺点:代码量多、学习成本高、简单页面没必要。
三、架构选择建议(新手必看)
项目规模 推荐架构 核心原因
小型 App(≤5 个页面) MVC 简单快速,不用额外学习成本
中型 App(5-20 个页面) MVVM 平衡开发效率和可维护性,解决 MVC 臃肿
大型 App(≥20 个页面) Clean Architecture / VIPER 极致解耦,支持长期维护和团队协作
组件化项目 VIPER / Clean 组件独立,便于多团队开发和复用
四、iOS 架构落地小技巧
避免过度设计:比如一个简单的工具类 App,用 MVC 就够了,不用强行上 MVVM;
巧用第三方库:MVVM 用 Combine(iOS 13+ 原生)或 RxSwift(兼容低版本);路由用 URLNavigator 或 SwiftUI NavigationStack(iOS 16+);
遵守 “单一职责”:任何类只做一件事(比如 ViewModel 只处理逻辑,不写 UI 代码;View 只显示,不处理逻辑);
SwiftUI 项目:优先用 MVVM(SwiftUI 天生支持数据绑定,@StateObject+@Published 完美适配)。
如果需要针对具体场景(比如 “SwiftUI + MVVM 完整项目示例”“组件化项目的 VIPER 拆分”)展开,或者想了解某类架构的详细落地步骤,可以告诉我!
介绍一下MVVM架构
MVVM 架构:核心逻辑、落地方式与实战细节
MVVM(Model-View-ViewModel)是一套以 “数据绑定” 为核心的解耦架构,核心目标是解决传统 MVC 中 ViewController 臃肿的问题 —— 通过新增 ViewModel 层接管所有业务逻辑,让 View(含 ViewController)只专注于 UI 显示和用户交互,最终实现 “职责清晰、可维护、可测试” 的代码结构。
它不是凭空创造的,而是 MVC 的 “进阶优化版”,尤其适配 iOS(SwiftUI/UIKit)、Android 等需要频繁更新 UI 的移动场景,现在已是中大型 App 的主流选择。
一、MVVM 四大核心角色(职责彻底拆分)
MVVM 把代码分成 4 个明确角色,每个角色只做自己的事,绝不越界:
角色 核心职责 通俗理解 iOS 中常见实现形式
Model 数据存储 + 数据处理(与 UI 完全无关) “数据仓库”:只管存数据、转数据 结构体 / 类(如 User、Product)、网络请求工具、数据库工具
View 显示 UI + 响应用户操作(不处理任何业务逻辑) “展示窗口”:只负责看和传指令 UIView、ViewController、SwiftUI View
ViewModel 业务逻辑 + 数据转换 + 状态管理(核心枢纽) “大脑”:接收 View 指令,处理后给数据 独立类(持有 Model,暴露可观察属性)
Binding View 与 ViewModel 的 “通信桥梁”(数据双向同步) “自动连接线”:数据变了自动更 UI iOS 原生 Combine、第三方 RxSwift、SwiftUI 自带绑定
关键原则:单向依赖
View 依赖 ViewModel(通过 Binding 监听数据),但 ViewModel 不依赖任何 View(不知道 View 的存在);
ViewModel 依赖 Model(调用 Model 拿数据、处理数据),但 Model 不依赖任何上层。
好处:修改 View 时不用动 ViewModel/Model,测试 ViewModel 时不用启动 UI,解耦彻底。
二、核心工作流程(一步看懂数据怎么跑)
以 “用户点击按钮,请求接口并显示用户信息” 为例,流程如下:
用户操作:用户点击 View(比如按钮),View 只做 “传话筒”,直接告诉 ViewModel:“我要获取用户数据”;
ViewModel 处理逻辑:ViewModel 收到指令后,调用 Model(比如网络工具)发起接口请求,同时更新 “加载状态”(isLoading = true);
Model 返回数据:Model 请求接口成功后,把原始 JSON 数据转换成 Model 实体(比如 User 类),返回给 ViewModel;
数据转换与通知:ViewModel 把 User 实体转换成 View 能直接显示的格式(比如给年龄加 “岁”),然后更新自己的 “可观察属性”(比如 user = 转换后的用户数据,isLoading = false);
Binding 自动同步:因为 View 已经通过 Binding 监听了 ViewModel 的属性,数据一更新,View 就自动刷新 UI(显示用户名、隐藏加载动画)。
整个过程中,View 从不用主动 “问” 数据,ViewModel 也不用主动 “推” 数据,全靠 Binding 自动同步,高效又解耦。
三、iOS 实战代码示例(Swift + UIKit + Combine)
下面用最常用的 “UIKit + Combine(原生绑定)” 实现一个简单场景,直观感受 MVVM 怎么写:

  1. Model 层(数据层:只管数据)
    swift
    // 1. 数据实体:存储用户信息(和 UI 无关)
    struct UserModel {
    let name: String
    let age: Int
    let avatarUrl: String
    }

// 2. 数据处理工具:模拟网络请求(实际项目中是 Alamofire/URLSession)
class UserRepository {
// 模拟接口请求:2 秒后返回数据
func fetchUser(userId: String, completion: @escaping (UserModel?) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
// 模拟接口返回的 JSON 数据
let mockJSON: [String: Any] = [
"name": "张三",
"age": 28,
"avatarUrl": "https://example.com/avatar.jpg"
]
// 转换为 Model 实体(实际项目用 Codable 解析)
let user = UserModel(
name: mockJSON["name"] as! String,
age: mockJSON["age"] as! Int,
avatarUrl: mockJSON["avatarUrl"] as! String
)
completion(user)
}
}
}
2. ViewModel 层(大脑:处理逻辑 + 暴露可观察数据)
swift
import Combine

class UserViewModel {
// 可观察属性:View 监听这些属性,数据变了自动更新 UI
@Published var userName: String = "" // 处理后的用户名(带前缀)
@Published var userAge: String = "" // 处理后的年龄(加“岁”)
@Published var avatarUrl: String = "" // 头像地址
@Published var isLoading: Bool = false // 加载状态
@Published var errorMessage: String? = nil // 错误信息

// 依赖 Model 层(通过构造函数注入,方便测试)
private let userRepository: UserRepository

// 用于取消 Combine 订阅(避免内存泄漏)
private var cancellables = Set<AnyCancellable>()

// 初始化:传入 Model 实例(依赖注入,解耦)
init(userRepository: UserRepository = UserRepository()) {
    self.userRepository = userRepository
}

// 业务逻辑:获取用户数据(View 调用这个方法)
func fetchUser(userId: String) {
    isLoading = true
    errorMessage = nil
    
    // 调用 Model 层的接口请求
    userRepository.fetchUser(userId: userId) { [weak self] user in
        guard let self = self else { return }
        self.isLoading = false
        
        if let user = user {
            // 数据转换:把 Model 转换成 View 能直接显示的格式
            self.userName = "昵称:\(user.name)"
            self.userAge = "年龄:\(user.age) 岁"
            self.avatarUrl = user.avatarUrl
        } else {
            self.errorMessage = "获取用户信息失败"
        }
    }
}

}
3. View 层(UI 层:只管显示和传指令)
swift
import UIKit
import Combine

class UserViewController: UIViewController {
// UI 组件
private let avatarImageView = UIImageView()
private let nameLabel = UILabel()
private let ageLabel = UILabel()
private let fetchButton = UIButton(type: .system)
private let loadingIndicator = UIActivityIndicatorView(style: .large)

// 持有 ViewModel(View 依赖 ViewModel)
private let viewModel = UserViewModel()
// 取消订阅的容器(避免内存泄漏)
private var cancellables = Set<AnyCancellable>()

override func viewDidLoad() {
    super.viewDidLoad()
    setupUI() // 纯 UI 布局,无业务逻辑
    bindViewModel() // 绑定 ViewModel 和 View
}

// 纯 UI 布局(和业务逻辑无关)
private func setupUI() {
    view.backgroundColor = .white
    
    // 布局代码省略(用 AutoLayout 或 SnapKit 均可)
    fetchButton.setTitle("获取用户信息", for: .normal)
    fetchButton.addTarget(self, action: #selector(fetchButtonTapped), for: .touchUpInside)
    
    loadingIndicator.hidesWhenStopped = true
    view.addSubview(avatarImageView)
    view.addSubview(nameLabel)
    view.addSubview(ageLabel)
    view.addSubview(fetchButton)
    view.addSubview(loadingIndicator)
}

// 核心:绑定 ViewModel 和 View(通过 Combine)
private func bindViewModel() {
    // 1. 监听用户名变化,更新 Label
    viewModel.$userName
        .receive(on: DispatchQueue.main) // 切换到主线程更新 UI
        .sink { [weak self] name in
            self?.nameLabel.text = name
        }
        .store(in: &cancellables)

    // 2. 监听年龄变化,更新 Label
    viewModel.$userAge
        .receive(on: DispatchQueue.main)
        .sink { [weak self] age in
            self?.ageLabel.text = age
        }
        .store(in: &cancellables)

    // 3. 监听加载状态,控制加载动画
    viewModel.$isLoading
        .receive(on: DispatchQueue.main)
        .sink { [weak self] isLoading in
            if isLoading {
                self?.loadingIndicator.startAnimating()
                self?.fetchButton.isEnabled = false
            } else {
                self?.loadingIndicator.stopAnimating()
                self?.fetchButton.isEnabled = true
            }
        }
        .store(in: &cancellables)

    // 4. 监听错误信息,弹出提示
    viewModel.$errorMessage
        .compactMap { $0 } // 过滤 nil
        .receive(on: DispatchQueue.main)
        .sink { [weak self] error in
            let alert = UIAlertController(title: "错误", message: error, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "确定", style: .default))
            self?.present(alert, animated: true)
        }
        .store(in: &cancellables)
}

// 用户点击按钮:只传指令给 ViewModel,不处理逻辑
@objc private func fetchButtonTapped() {
    viewModel.fetchUser(userId: "1001") // 直接调用 ViewModel 的方法
}

}
代码亮点:
ViewController 里几乎没有业务逻辑,只有 “UI 布局” 和 “绑定”,彻底告别 “大泥球”;
ViewModel 只处理逻辑,不关心 UI 怎么显示,就算后续把 UI 改成 SwiftUI,ViewModel 完全不用动;
通过 @Published(可观察属性)和 sink(监听)实现 Binding,原生无依赖。
四、MVVM 的核心优势(为什么大家都在用)
彻底解决 ViewController 臃肿:业务逻辑全移到 ViewModel,ViewController 代码量大幅减少,维护成本降低;
可测试性强:ViewModel 不依赖 UI,不用启动 App,直接写单元测试就能验证逻辑(比如测试 “接口返回 nil 时是否显示错误信息”);
数据自动同步:Binding 机制让 “数据变 → UI 变”,不用手动调用 label.text = xxx,减少重复代码;
解耦彻底:各层职责清晰,修改某一层不影响其他层(比如换网络库、换 UI 样式);
适配 SwiftUI:SwiftUI 天生支持数据绑定(@StateObject+@Published),和 MVVM 完美契合,是 SwiftUI 项目的首选架构。
五、常见误区(新手必避)
把 ViewModel 写成 “超级工具类”:ViewModel 只处理当前 View 相关的逻辑,不要把全局工具方法(比如日期转换)塞进去;
ViewModel 持有 View 引用:绝对不能让 ViewModel 持有 ViewController 或 UIView 的强引用,否则会内存泄漏(用 [weak self] 或依赖注入);
过度依赖第三方库:iOS 13+ 原生 Combine 已经能满足大部分场景,不用强行上 RxSwift(学习成本高);
简单项目强行用 MVVM:如果只是一个工具类 App(比如计算器),用 MVC 就够了,MVVM 会增加不必要的复杂度;
忽略线程安全:网络请求、数据库操作都在子线程,更新 @Published 属性后,一定要用 receive(on: DispatchQueue.main) 切换到主线程更新 UI。
六、iOS 中实现 Binding 的 3 种方式(按需选择)
方式 优点 缺点 适用场景
Combine(原生) 无第三方依赖、iOS 原生支持、API 简洁 仅支持 iOS 13+ iOS 13+ 的 UIKit/SwiftUI 项目
RxSwift(第三方) 功能强大、支持 iOS 低版本、生态完善 学习成本高、需引入第三方库 复杂业务(比如流式数据处理)、低版本兼容
SwiftUI 自带绑定 零代码绑定、和 SwiftUI 深度整合 仅支持 SwiftUI SwiftUI 项目
总结
MVVM 的核心不是 “多写一个类”,而是 “通过职责拆分和数据绑定,让代码更易维护、可测试”。它比 MVC 解耦更彻底,比 Clean Architecture 更轻量,是中大型 iOS 项目的 “性价比之选”。

posted @ 2025-11-20 11:21  福寿螺888  阅读(58)  评论(0)    收藏  举报