项目架构
Mvc&mvvm

MVC & MVVM

MVC / MVVM 设计模式

Q1:一句话解释 MVC 与 MVVM?

A:

  • MVC:把业务拆成 Model(数据/业务)View(展示)Controller(协调/事件),但在 iOS 里常演化成 “胖控制器”
  • MVVM:在 MVC 基础上新增 ViewModel(状态+UI 无关业务),通过 数据绑定 驱动界面,减轻 Controller / View 的压力。

Q2:两者在 iOS 中的典型落地分别是什么?

A:

  • MVC(UIKit 经典):ViewController 既做视图生命周期、事件分发,又装状态、调网络、做跳转,易臃肿。
  • MVVM(UIKit/SwiftUI 通吃):把可显示的状态变更逻辑放进 ViewModel;VC/SwiftUI View 仅渲染状态转发用户意图,用 Combine/RxSwift/KVO 绑定刷新。

结构与职责

Q3:各角色各做什么(面试官爱考的“职责边界”)?

A:

  • Model:业务实体、领域逻辑、数据访问接口(Repository)。
  • View(或 SwiftUI View):纯展示 + 用户操作捕获,不持久保存业务状态。
  • Controller(MVC):系统事件协调、View 组装、导航;尽量不写业务。
  • ViewModel(MVVM)可观察状态(loading/error/data)、输入→输出变换、与 UseCase/Repository 协作,不直接依赖 UIKit。

Q4:MVC 为什么会“胖控制器”?如何规避?

A:

  • 成因:VC 天生承接生命周期、路由、UI 更新;业务/网络也被塞进来,职责上瘾叠加。
  • 规避
    1. 把网络/持久化下沉到 Repository/UseCase
    2. 视图细节下沉到 自定义 View/Cell
    3. 流程/跳转抽到 Coordinator
    4. 复用逻辑提升到 ViewModel(转 MVVM)。

选择与权衡

Q5:什么时候选 MVC,什么时候选 MVVM?

A:

  • MVC:原型快速迭代、小页面、生命周期/动画为主、团队不熟响应式绑定。
  • MVVM:复杂状态、多页面复用逻辑、强交互/频繁刷新、需要较高可测试性可维护性、SwiftUI 项目。

Q6:MVVM 的优势与代价?

A:

  • 优势:瘦 VC/瘦 View、状态集中、逻辑复用、可测试性强、与 SwiftUI 天然契合。
  • 代价:需要响应式思维与绑定工具;双向绑定不当会产生循环/难排错;首期代码量略增。

绑定与响应式

Q7:不引第三方如何做绑定?

A:

  • KVO:老派,易脆弱;
  • Notification/Delegate/Closure:手动推送变更;
  • Combine(推荐):@Published、AnyPublisher 驱动 UI;
  • SwiftUI:@State/@ObservedObject/@EnvironmentObject 与 @Published 协同,最顺滑。

Q8:RxSwift vs Combine 简要对比?

A:

  • 生态:RxSwift 成熟跨平台/跨框架;Combine 原生、语义更贴 Swift。
  • 依赖:Rx 需三方库;Combine iOS 13+ 原生。
  • API 风格:思想一致(可观察流),语法与算子命名略有差异。

测试、解耦与工程化

Q9:谁更利于测试?怎么测?

A:

  • MVVM 更易测:ViewModel 纯 Swift,无 UIKit,可直接对输入事件(Intent)与输出状态(State)做单测;
  • 要点:Repository、时间/调度器用 协议 + 依赖注入 注入 Mock,隔离网络与异步。

Q10:Coordinator、DI 和 MVVM 如何配合?

A:

  • Coordinator:管理路由/页面装配,VC/VM 不关心跳转细节;
  • DI(依赖注入):在装配层为 ViewModel 注入 UseCase/Repository/Tracker,可替换可 Mock

实战易错点

Q11:网络层与 Model 放哪?需要 DTO → Domain 转换吗?

A:

  • Repository 隔离外部世界(HTTP/DB/缓存),对上暴露 领域模型
  • DTO 与 Domain 分离,在 Repository 内转换,避免 API 字段泄漏到上层。

Q12:双向绑定有哪些风险?如何防护?

A:

  • 风险:循环更新、状态不一致、线程问题。
  • 防护:单向数据流优先;必须双向时加 去抖/节流distinctUntilChanged、明确主线程调度、在 ViewModel 做最小可变状态聚合。

Q13:列表页面在两种模式下如何组织?

A:

  • MVC:VC 做数据源/代理,易臃肿;
  • MVVM:ViewModel 暴露 [CellViewModel]Sectioned State,VC/Cell 只读状态 + 绑定事件回传(如 didSelect(at:) → VM Intent)。

Q14:全局的 Loading / Error / Empty(LEE)状态怎么设计?

A:

  • ViewModel 中统一建模:
enum ViewStatus { case idle, loading, success, empty, error(Error) }
struct ViewState { var status: ViewStatus; var data: [Item] }
  • 视图根据 status 切视图,避免散落的 if/else。

极简 MVVM 示例(Combine 版,便于面试白板讲解)

// Model
struct Repo: Decodable { let name: String }

// UseCase
protocol FetchRepos {
    func callAsFunction(for user: String) -> AnyPublisher<[Repo], Error>
}

// ViewModel
final class RepoListVM: ObservableObject {
    @Published private(set) var state = ViewState(status: .idle, data: [])
    private let fetchRepos: FetchRepos
    private var bag = Set<AnyCancellable>()

    init(fetchRepos: FetchRepos) { self.fetchRepos = fetchRepos }

    // Intent:用户触发加载
    func load(user: String) {
        state.status = .loading
        fetchRepos(for: user)
            .receive(on: DispatchQueue.main)
            .map { $0.isEmpty ? ViewStatus.empty : ViewStatus.success }
            .sink(
              receiveCompletion: { if case .failure(let e) = $0 { self.state.status = .error(e) } },
              receiveValue: { status in self.state.status = status }
            )
            .store(in: &bag)
    }
}

讲解要点(背诵版)

  • View 只订阅 state;
  • Intent(load)由用户动作触发;
  • Repository/UseCase 注入、可 Mock;
  • 主线程切换在 VM 内部统一处理。

一句话对比小抄(上岸背诵)

  • MVC:快而简,易胖,适合小页面与原型。
  • MVVM:状态集中、易测、可复用,首期成本略高,SwiftUI 首选。
  • 瘦 VC 的路径:下沉到 ViewModel / View / Coordinator / Repository。
  • 测试抓手:VM 不依赖 UIKit;用协议 + Mock 隔离外界。
  • 绑定守则:单向数据流优先,必须双向则做去抖/去重/主线程调度。

以上内容已按“从 H2 开始的带答案问答版”整理完毕,适合面试快速背诵与延展说明,如需我继续补充 VIPER/MVI/Redux 风格对比常见追问清单,我可以直接续写并追加答题模板。