一、iOS 主流架構對比(從簡單到複雜)
|
架構
|
核心分層
|
適用場景
|
優點
|
缺點
|
|
MVC |
Model(數據)+ View(視圖)+ Controller(控制器)
|
小型 App / 快速原型 / 工具類 App
|
蘋果原生支持、上手快、代碼量少
|
Controller 易成 “大泥塊”,耦合高
|
|
MVP |
Model + View + Presenter(主持人)
|
中型 App / 需要單元測試的場景
|
解耦 View 和 Controller,測試友好
|
Presenter 可能臃腫,多一層轉發邏輯
|
|
MVVM |
Model + View + ViewModel(視圖模型)
|
中大型 App / 響應式開發(RxSwift/Combine)
|
數據和視圖雙向綁定,UI 邏輯複用
|
ViewModel 學習成本高,需配合響應式框架
|
|
Clean Architecture(整潔架構) |
實體層 + 用例層 + 接口層 + 外部層(UI / 網絡 / 存儲)
|
大型 App / 團隊協作 / 長期維護的項目
|
極致解耦,可測試性極強,易擴展
|
架構複雜,初期開發成本高
|
|
VIPER |
View + Interactor + Presenter + Entity + Router
|
超大型 App / 多人協作的商業項目
|
職責拆分極致,模塊獨立
|
模板代碼多,開發效率低
|
二、核心架構詳解(白話 + 代碼示例)
1. MVC(最基礎,蘋果原生推薦)
核心邏輯
- Model:處理數據(網絡請求、本地存儲、數據解析),不關心 UI;
- View:純展示(UIView/UILabel/UIButton),不包含業務邏輯;
- Controller(UIViewController):連接 Model 和 View,處理用户交互、數據傳遞。
實戰代碼示例(簡易新聞列表)
swift
// 1. Model:純數據模型,處理數據邏輯
struct NewsModel {
let title: String
let content: String
let publishTime: String
// 模擬網絡請求獲取數據
static func fetchNews(completion: @escaping ([NewsModel]) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
let mockData = [
NewsModel(title: "iOS 18 新特性", content: "新增AI助手...", publishTime: "2025-01-01"),
NewsModel(title: "Swift 6 發佈", content: "支持併發優化...", publishTime: "2025-01-02")
]
completion(mockData)
}
}
}
// 2. View:純展示,無業務邏輯
class NewsCell: UITableViewCell {
private let titleLabel = UILabel()
private let timeLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// 佈局代碼(省略)
contentView.addSubview(titleLabel)
contentView.addSubview(timeLabel)
}
// 給View賦值,只做展示
func config(with model: NewsModel) {
titleLabel.text = model.title
timeLabel.text = model.publishTime
}
required init?(coder: NSCoder) { fatalError() }
}
// 3. Controller:核心調度,連接Model和View
class NewsVC: UIViewController {
private let tableView = UITableView()
private var newsList: [NewsModel] = []
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
loadData()
}
private func setupUI() {
view.addSubview(tableView)
tableView.register(NewsCell.self, forCellReuseIdentifier: "NewsCell")
tableView.dataSource = self
}
private func loadData() {
// 調用Model獲取數據,更新View
NewsModel.fetchNews { [weak self] news in
self?.newsList = news
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
extension NewsVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell", for: indexPath) as! NewsCell
cell.config(with: newsList[indexPath.row])
return cell
}
}
痛點
- Controller 會越寫越大(既管 UI 佈局,又管數據請求,還管業務邏輯);
- View 和 Model 間接耦合(Controller 依賴兩者,修改一方可能影響另一方)。
2. MVVM(中大型項目首選,配合響應式更絲滑)
核心邏輯
- 在 MVC 基礎上,把 Controller 中的UI 邏輯 / 數據轉換邏輯抽離到 ViewModel;
- View(Controller)只負責 “訂閲” ViewModel 的數據,ViewModel 不依賴 View;
- 推薦配合 Combine/RxSwift 實現數據雙向綁定(數據變 → UI 自動更)。
實戰代碼示例(基於上面的 MVC 改造)
swift
// 1. Model:和MVC一致,無變化
struct NewsModel { /* 同上 */ }
// 2. ViewModel:核心,處理UI邏輯+數據轉換,不依賴UIKit
class NewsViewModel {
// Combine 發佈者:數據變化時自動通知View
@Published var newsList: [NewsModel] = []
@Published var isLoading: Bool = false
// 數據請求邏輯
func fetchNews() {
isLoading = true
NewsModel.fetchNews { [weak self] news in
self?.isLoading = false
self?.newsList = news
}
}
// UI邏輯:比如處理標題顯示樣式(ViewModel 負責,View 只展示)
func getTitleStyle(for news: NewsModel) -> [NSAttributedString.Key: Any] {
return [.font: UIFont.boldSystemFont(ofSize: 16), .foregroundColor: UIColor.black]
}
}
// 3. View(Controller):只訂閲ViewModel數據,不處理業務邏輯
class NewsVC: UIViewController {
private let tableView = UITableView()
// 持有ViewModel
private let viewModel = NewsViewModel()
// 存儲Combine訂閲,防止銷燬
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
bindViewModel() // 綁定ViewModel和View
viewModel.fetchNews()
}
private func setupUI() { /* 同上 */ }
// 核心:數據綁定,ViewModel變 → View自動更
private func bindViewModel() {
// 監聽新聞列表變化,刷新表格
viewModel.$newsList
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.tableView.reloadData()
}
.store(in: &cancellables)
// 監聽加載狀態,顯示/隱藏加載框
viewModel.$isLoading
.receive(on: DispatchQueue.main)
.sink { [weak self] isLoading in
if isLoading {
self?.showLoading()
} else {
self?.hideLoading()
}
}
.store(in: &cancellables)
}
private func showLoading() { /* 顯示加載框 */ }
private func hideLoading() { /* 隱藏加載框 */ }
}
extension NewsVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.newsList.count // 直接取ViewModel的數據
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell", for: indexPath) as! NewsCell
let news = viewModel.newsList[indexPath.row]
cell.config(with: news, style: viewModel.getTitleStyle(for: news)) // 取ViewModel的UI樣式
return cell
}
}
// 改造NewsCell,接收ViewModel傳遞的樣式
extension NewsCell {
func config(with model: NewsModel, style: [NSAttributedString.Key: Any]) {
titleLabel.attributedText = NSAttributedString(string: model.title, attributes: style)
timeLabel.text = model.publishTime
}
}
優勢
- Controller 代碼量大幅減少,只負責 UI 佈局和數據綁定;
- ViewModel 純邏輯層,不依賴 UIKit,可單獨寫單元測試;
- 數據雙向綁定,避免手動調用
reloadData()等重複代碼。
3. Clean Architecture(整潔架構,大型項目 / 團隊協作)
核心邏輯
- 分層原則:內層不依賴外層,外層依賴內層(核心是業務邏輯,不是 UI / 網絡);
- 四層結構(從內到外):
實戰目錄結構(關鍵是分層解耦)
plaintext
YourApp/
├── Core/ // 核心層(內層,不依賴外部)
│ ├── Entity/ // 實體:News.swift
│ └── UseCase/ // 用例:FetchNewsUseCase.swift
├── Data/ // 接口適配層:數據轉換/網絡/存儲
│ ├── Repository/ // 倉庫:NewsRepository.swift(封裝網絡/本地數據)
│ ├── DTO/ // 數據傳輸對象:NewsDTO.swift(網絡返回格式)
│ └── Mapper/ // 映射:NewsMapper.swift(DTO ↔ Entity)
├── Presentation/ // 外部層:UI相關(MVVM的View+ViewModel)
│ ├── View/ // NewsVC.swift、NewsCell.swift
│ └── ViewModel/ // NewsViewModel.swift
└── Infrastructure/ // 外部層:工具類(網絡、存儲、第三方庫)
├── Network/ // NetworkManager.swift
└── Storage/ // LocalStorage.swift
核心代碼示例(用例層 + 倉庫層)
swift
// 1. Core/Entity:純業務實體,和技術無關
struct News {
let id: String
let title: String
let content: String
let publishTime: Date
}
// 2. Core/UseCase:業務邏輯,定義“獲取新聞”的用例
protocol FetchNewsUseCase {
func execute() async throws -> [News]
}
class DefaultFetchNewsUseCase: FetchNewsUseCase {
private let newsRepository: NewsRepository // 依賴抽象,不是具體實現
init(newsRepository: NewsRepository) {
self.newsRepository = newsRepository
}
// 核心業務邏輯(比如過濾過期新聞)
func execute() async throws -> [News] {
let allNews = try await newsRepository.getNews()
// 業務邏輯:只返回7天內的新聞
let recentNews = allNews.filter { news in
Calendar.current.dateComponents([.day], from: news.publishTime, to: Date()).day! <= 7
}
return recentNews
}
}
// 3. Data/Repository:抽象倉庫(接口)
protocol NewsRepository {
func getNews() async throws -> [News]
}
// 4. Data/Repository:具體實現(網絡請求)
class RemoteNewsRepository: NewsRepository {
private let networkManager: NetworkManager
init(networkManager: NetworkManager) {
self.networkManager = networkManager
}
func getNews() async throws -> [News] {
// 網絡請求獲取DTO
let dtoList: [NewsDTO] = try await networkManager.request(url: "https://api.news.com/list")
// DTO轉Entity
return dtoList.map { NewsMapper.map(from: $0) }
}
}
// 5. Data/DTO + Mapper:數據轉換
struct NewsDTO: Codable {
let id: String
let title: String
let content: String
let publishTime: String // 網絡返回字符串格式
}
enum NewsMapper {
static func map(from dto: NewsDTO) -> News {
// 轉換時間格式等邏輯
let publishTime = DateFormatter.iso8601.date(from: dto.publishTime)!
return News(
id: dto.id,
title: dto.title,
content: dto.content,
publishTime: publishTime
)
}
}
// 6. Presentation/ViewModel:調用用例層
class NewsViewModel {
@Published var newsList: [News] = []
@Published var isLoading: Bool = false
private let fetchNewsUseCase: FetchNewsUseCase // 依賴抽象,可替換實現
init(fetchNewsUseCase: FetchNewsUseCase) {
self.fetchNewsUseCase = fetchNewsUseCase
}
func fetchNews() {
isLoading = true
Task {
do {
let news = try await fetchNewsUseCase.execute()
await MainActor.run {
self.newsList = news
self.isLoading = false
}
} catch {
await MainActor.run {
self.isLoading = false
// 處理錯誤
}
}
}
}
}
// 7. 組裝(DI依賴注入)
class NewsVC: UIViewController {
private let viewModel: NewsViewModel
// 依賴注入,外部傳ViewModel(方便測試時替換用例)
init(viewModel: NewsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
// 實際使用時,組裝依賴
static func create() -> NewsVC {
let networkManager = NetworkManager()
let repository = RemoteNewsRepository(networkManager: networkManager)
let useCase = DefaultFetchNewsUseCase(newsRepository: repository)
let viewModel = NewsViewModel(fetchNewsUseCase: useCase)
return NewsVC(viewModel: viewModel)
}
}
優勢
- 極致解耦:比如替換網絡庫(從 Alamofire 換成 URLSession),只改
RemoteNewsRepository,不影響核心業務邏輯; - 可測試性:用例層可單獨測試,無需啓動 App;
- 團隊協作:不同開發者負責不同層(比如 A 寫核心用例,B 寫 UI,C 寫網絡),互不干擾。