一、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 / 網絡);
  • 四層結構(從內到外):
  1. Entity(實體層):核心業務模型(比如用户、訂單),和技術無關;
  2. Use Case(用例層):業務邏輯(比如 “登錄”“下單”“獲取新聞列表”);
  3. Interface Adapter(接口適配層):把用例層的數據轉換為外部可用的格式(比如轉 Model、轉 DTO);
  4. External(外部層):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 寫網絡),互不干擾。

三、架構選擇建議(新手必看)

  1. 小項目 / 工具類 App:直接用 MVC,快速開發,不用過度設計;
  2. 中大型 App / 需要單元測試:優先 MVVM(配合 Combine/RxSwift),平衡開發效率和可維護性;
  3. 超大型 App / 多人協作 / 長期維護:Clean Architecture,前期成本高,但後期擴展、重構成本極低;
  4. 避免過度設計:比如小項目用 Clean Architecture,會導致模板代碼多、開發效率低。

總結

  1. iOS 架構的核心是解耦:把數據、邏輯、UI 分開,降低修改和維護成本;
  2. MVC 是基礎,MVVM 是中大型項目主流,Clean Architecture 是大型項目的 “終極方案”;
  3. 選擇架構的關鍵是匹配項目規模,不是越複雜越好,夠用且易維護就是最好的。