MVC / MVP / MVVM 架构解析
<blockquote><p>认真对待每时、每刻每一件事,把握当下、立即去做。</p>
</blockquote>
<p>MVC 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。下面主要对 MVC 架构下的优化方案以及其项目结构解析。</p>
<p><img src="https://s2.loli.net/2025/09/10/XOsi9L2Mfq6mZvg.jpg"></p>
<h2 id="一-mvc-相应层应该做什么">一. MVC 相应层应该做什么?</h2>
<h3 id="1-控制器controller业务层">1. 控制器(Controller)业务层</h3>
<p>控制器(Controller)-->业务层, Model 与 View 层的中介,负责转发请求,对请求进行处理,把 Model 数据在 View 上展示出来。</p>
<p><strong>主要职责:</strong></p>
<ul>
<li>管理 View Container 的生命周期;</li>
<li>负责生成所有的 View 实例,并放入 View Container;</li>
<li>监听来自 View 与业务有关的事件,通过与 Model 的合作,来完成对应事件的业务;</li>
</ul>
<h3 id="2-视图view展现层">2. 视图(View)展现层</h3>
<p>视图(View) -->展现层,承载 UI 展示和事件响应(交互)。</p>
<p><strong>主要职责:</strong></p>
<ul>
<li>响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在 View 去做)等。</li>
<li>界面元素表达;</li>
</ul>
<h3 id="3-模型model数据层">3. 模型(Model)数据层</h3>
<p>模型(Model) -->数据层,数据处理层,包括网络请求,数据加工,算法实现等。</p>
<p><strong>主要职责:</strong></p>
<ul>
<li>给 ViewController 提供数据;</li>
<li>给 ViewController 存储数据提供接口;</li>
<li>提供经过抽象的业务基本组件,供 Controller 调度;</li>
</ul>
<p><img src="https://s2.loli.net/2025/09/10/czLInxjQAO6GDPU.png"></p>
<h3 id="4-示例解析">4. 示例解析</h3>
<p>在 iOS 中的 <code>Controlller</code> 是<code>UIViewController</code>,所以导致很多人会把<code>视图</code>写在 <code>Controller</code> 中,如下图:</p>
<pre><code class="language-objectivec">@implementation DemoViewController
- (void)viewDidLoad {
;
//setupUI
//1.createView
UIView *view = [init];
view.frame = CGRectMake(100, 100, 100, 100);
view.backgroundColor = ;
;
//2.createButton
UIButton *btn = ;
btn.center = self.view.center;
;
//3...
}
</code></pre>
<p>这种写法在我刚学习编程的时候也这样写过,先说这样写的好处,以及初学者为什么会这么写:</p>
<ul>
<li>比如按钮,可以在当前控制器直接 <code>add target:</code> 添加点击事件,在当前控制器内就能调用到点击方法,不需要设置代理之类的;</li>
<li>比如要找某个界面,直接切到这个界面对应的 <code>controller</code> 就行,因为<code>View</code> 写在 <code>Controller</code> 里面,不用去别的地方找就这里有;</li>
<li>比如一个 View,里面有一张图片,图片依赖于网络资源,这样写的好处,可以直接让 <code>View</code> 在 <code>Controller</code> 中就能拿到资源,不需要传值;</li>
</ul>
<p>缺点:</p>
<ul>
<li>导致 <code>Controller</code> 特别臃肿,里面代码特别多,视图一复杂起来,代码量可能过1000行,不好维护;</li>
<li>写在 <code>Controller</code> 里无法复用,除非你在 VC2 里面 copy 当前 VC 中的<code>View</code> 的代码;</li>
<li>特别low!!会被懂架构的人瞧不起,喷你根本不是 <code>MVC</code>,是 <code>MC</code> 架构;</li>
</ul>
<p>如何告别 <code>MC</code> 模式,真正走到 <code>MVC</code>?</p>
<p>先给自己洗脑,<code>iOS</code> 的 <code>Controller</code> 不是 <code>UIViewController</code>,而是普通的 <code>Controller</code>,没有 <code>View</code>。(很关键的一步)。</p>
<p>模块化划分,每个模块对应自己的一个 View,例如 Demo 模块,View 层里面有个 <code>DemoView</code>,将界面元素写到 View 中。</p>
<h2 id="二-mvc-相应层之间如何通信">二. MVC 相应层之间如何通信?</h2>
<p><img src="https://s2.loli.net/2025/09/10/Fn1IZJAXbQ2aMHy.jpg"></p>
<h3 id="1-view-层和-controller-层双向通信">1. View 层和 Controller 层双向通信</h3>
<h4 id="11-controller-如何将数据传递到-view-层">1.1 Controller 如何将数据传递到 View 层</h4>
<ul>
<li>创建 View 的时候通过 View 的函数作为外部参数传进去。</li>
</ul>
<h4 id="12-view-层用户事件如何传递到-controller-层">1.2 View 层(用户事件)如何传递到 Controller 层</h4>
<h5 id="121-代理delegate">1.2.1 代理(delegate)</h5>
<p>通过代理(delegate),代理委托模式通过定义协议方法实现解耦, View 只关心事件触发不处理具体逻辑;</p>
<pre><code class="language-objective-c">// 1. 定义协议
@protocol CustomViewDelegate <NSObject>
- (void)customView:(UIView *)view didTapButton:(UIButton *)button;
@end
// 2. View 持有 delegate 弱引用
@interface CustomView : UIView
@property (nonatomic, weak) id<CustomViewDelegate> delegate;
@end
@implementation CustomView
- (void)buttonTapped:(UIButton *)sender {
; // 触发代理方法
}
@end
// 3. Controller 实现协议
@interface ViewController () <CustomViewDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
CustomView *view = [ init];
view.delegate = self; // 设置代理
}
- (void)customView:(CustomView *)view didTapButton:(UIButton *)button {
NSLog(@"Delegate: 按钮点击事件处理"); // Controller 响应事件
}
@end
</code></pre>
<h5 id="122-target-action-监听">1.2.2 target-action 监听</h5>
<p>在 Controller 设置 target-action 监听,Controller 给 View 添加一个 target,当用户的触摸事件发生时,view 产生 action,Controller 接收到之后做出相应的响应,直接建立 View 与控制器的响应链关系,适合简单控件事件;</p>
<pre><code class="language-objective-c">// 1. View 暴露添加 target 的方法
@interface CustomView : UIView
- (void)addTarget:(id)target action:(SEL)action;
@end
@implementation CustomView {
id _target;
SEL _action;
}
- (void)addTarget:(id)target action:(SEL)action {
_target = target;
_action = action;
}
- (void)buttonTapped {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
; // 执行 Action
#pragma clang diagnostic pop
}
@end
// 2. Controller 设置 Target-Action
@implementation ViewController
- (void)viewDidLoad {
CustomView *view = [ init];
; // 绑定事件
}
- (void)handleButtonTap:(CustomView *)sender {
NSLog(@"Target-Action: 按钮点击事件处理"); // Controller 响应事件
}
@end
</code></pre>
<h5 id="123-数据源模式-data-source">1.2.3 数据源模式 data source</h5>
<p>通过数据源模式 data source,通过数据驱动 UI 更新,控制器实现数据获取协议供 View 调用;</p>
<pre><code class="language-objective-c">// 1. 定义数据源协议
@protocol CustomViewDataSource <NSObject>
- (NSString *)textForButtonInView:(CustomView *)view;
@end
// 2. View 持有 dataSource 引用
@interface CustomView : UIView
@property (nonatomic, weak) id<CustomViewDataSource> dataSource;
- (void)reloadData; // 触发数据更新
@end
@implementation CustomView
- (void)reloadData {
NSString *text = ; // 获取数据
;
}
@end
// 3. Controller 实现数据源
@interface ViewController () <CustomViewDataSource>
@end
@implementation ViewController
- (void)viewDidLoad {
CustomView *view = [ init];
view.dataSource = self;
; // 初始化数据
}
- (NSString *)textForButtonInView:(CustomView *)view {
return @"DataSource 模式"; // 提供动态数据
}
@end
</code></pre>
<h5 id="124-block闭包">1.2.4 Block(闭包)</h5>
<p>Block(闭包):View 定义闭包属性,Controller 通过赋值闭包来响应事件。优点,代码紧凑,适合简单回调。缺点,需注意循环引用(使用 <code></code>)。</p>
<pre><code class="language-objective-c">class CustomView: UIView {
var onButtonTap: (() -> Void)?
@objc func buttonTapped() { onButtonTap?() }
}
// Controller 中赋值
customView.onButtonTap = { in self?.handleTap() }
</code></pre>
<h3 id="2-model-层和-controller-层双向通信">2. Model 层和 Controller 层双向通信</h3>
<p>我们来看下这里的 Model 层通信,先看一段代码。</p>
<pre><code class="language-objectivec">@implementation DemoViewController
- (void)viewDidLoad {
;
//loadDatas
[GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id_Nullable responseObject)
{
//刷新tableView
_datas = responseObject;
;
} failure:nil];
}
</code></pre>
<p>这种写法在我刚学习编程的时候也这样写过,先说这样写的好处,以及初学者为什么会这么写:</p>
<ul>
<li>简单,网络请求完,直接在当前控制器刷新 <code>TableView</code> 的数据源;</li>
<li>比如要找某个界面的网络请求,直接切到这个界面对应的 <code>controller</code> 就行,因为数据请求 写在<code>Controller</code> 里面,不用去别的地方找,就这里有;</li>
<li>比如当前网络请求接口,需要外部参数,比如前一个界面的 <code>uuid</code>,这样写的好处,可以直接让当前请求在 <code>Controller</code> 中就能拿到资源,不需要传值;</li>
</ul>
<p>缺点:</p>
<ul>
<li>又导致 <code>Controller</code> 特别臃肿,里面代码特别多,如果当前控制器需要多次请求,代码量可能过1000行,不好维护;</li>
<li>写在 <code>Controller</code> 里无法复用,除非你在 VC2 里面 copy 当前 VC 中的 <code>网络请求</code>的代码;</li>
<li>如果某些接口有依赖要求,接口1请求完再请求接口2,需要嵌套起来;</li>
<li>特别 low!!会被懂架构的人瞧不起,喷你根本不是 <code>MVC</code>,如果你还用了上面的 <code>View</code> 写在 <code>Controller</code> 的操作的话,恭喜你,最终大法 -<code>Controller 架构</code> 顺利完成,并不需要什么 <code>Model</code> && <code>View</code>;</li>
</ul>
<p>这 <code>iOS</code> 的 <code>Controller</code> 就算是 <code>UIViewController</code>,也没看到 <code>Model</code> 啊,没有 <code>Model</code>。(很关键的一步);</p>
<p>模块化划分,每个模块对应自己的一个 Model,例如 Demo 模块,Model 层里面有个 <code>DemoModel</code>,将网络请求&&数据处理写到 <code>Model</code> 中;</p>
<h4 id="21--controller-调用和传值到-model">2.1Controller 调用和传值到 Model</h4>
<p>Controller 层直接调用 Model 层类方法和实例方法,并通过参数传值。</p>
<h4 id="22-model-层数据如何回调到-controller-层">2.2 Model 层数据如何回调到 Controller 层</h4>
<p>Model 层数据如何回调到 Controller 层,Controller 层如何知道 Model 层数据发生了改变。</p>
<h5 id="221-block-回调">2.2.1 Block 回调</h5>
<p>轻量级单向通信,适合简单回调但需注意循环引用</p>
<pre><code class="language-objective-c">//Model
@implementation DemoModel
+ (void)fetchDatasWithUUid:(NSString *)uuid success:(successBlock)block{
//Model发送网络请求
NSDictionary *parameters = @{@"uuid":uuid}
[GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id_Nullable responseObject)
{
//通过block异步回调~
block(responseObject);
} failure:nil];
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
;
//loadDatas
[DemoModel fetchDatasWithUUid:_uuid success:^(NSArray *array) {
_datas = array;
;
}];
}
</code></pre>
<h5 id="222-kvo监听">2.2.2 KVO(监听)</h5>
<p>KVO(监听),监听 Model 的每个属性的变化来做出响应;</p>
<pre><code class="language-objective-c">// Model.h
@interface MyModel : NSObject
@property (nonatomic, strong) NSString *data;
@end
// Controller.m
- (void)viewDidLoad {
;
[self.model addObserver:self
forKeyPath:@"data"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if () {
self.label.text = change; // 响应变化
}
}
- (void)dealloc {
;
}
</code></pre>
<h5 id="223-notification通知">2.2.3 Notification(通知)</h5>
<p>Notification(通知),Model 中创建一个 NSNotificationCenter,在 Controller中创建一个方法来接收通知。当 Model 发生变化时,他会发送一个通知,而 Controller 会接收通知,一对多广播式通信,适合跨模块解耦但性能开销较大。</p>
<p><img src="https://s2.loli.net/2025/09/10/514ykIUH3FWaGui.png"></p>
<p>解释一下上面这幅图,一个完整的模块被分为了三个相对独立的部分,分别是Model,View,Controller,对应到我们 App 中的依次为继承自 NSObject 的数据中心,承载 UI 展示和事件响应的 View 以及我们最最常用的 UIViewController。</p>
<p>其中 VC 持有 View 和 Model 部分,View 通过代理或者 Target-Action 的方式把用户的操作传递给 VC,VC 负责根据不同的用户行为做出不同响应。如果需要加载或刷新数据则直接调用 Model 暴露的接口,如果数据可以同步拿到,则直接使用获取到的数据刷新 View。如果数据需要通过网络请求等其他异步的方式获取,VC 则通过监听 Model 发出的数据更新(成功或失败)通知,在收到通知时根据成功或者失败对 View 进行相应的刷新操作。可以看出来整个过程中 View 和 Model 是没有直接交互的,所有的操作都是通过 VC 进行协调的。</p>
<p>基础的 MVC 讲解完毕,其实本质上就是让 Controller 减压,不该控制器管的他别让他知道,如上基础 <code>MVC</code> 操作之后的优势:</p>
<ul>
<li>MVC 架构分明,在同一个模块内,如果视图有问题,找到该模块的 <code>View</code> 就行,其他同理,<code>Controller</code> 代码大大减少,负责 <code>View</code> 的代理事件就可以;</li>
<li>可以复用,比如你一个产品列表的数据,首页也要用,产品页也要用,直接分别在其对应的 <code>VC1</code> && <code>VC2</code> 调用函数 <code></code> 即可,无需写多次,View 的复用同理;</li>
<li>结构分明,便于维护,拓展也是在此基础上拓展,代码干净简洁。</li>
</ul>
<h2 id="三-mvc-架构常见的疑惑">三. MVC 架构常见的疑惑</h2>
<h3 id="1-遗失的网络逻辑网络数据请求应该放在那里">1. 遗失的网络逻辑(网络数据请求应该放在那里?)</h3>
<p>苹果使用的 MVC 的定义是这么说的:所有的对象都可以被归类为一个 Model,一个 View,或是一个控制器。就这些,那么把网络代码放哪里?和一个 API 通信的代码应该放在哪儿?</p>
<p>你可能试着把它放在 Model 对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的 Model 生命周期更长,事情将变的复杂。显然也不应该把网络代码放在 View 里,因此只剩下控制器了。这同样是个坏主意,因为这加剧了厚重控制器的问题。那么应该放在那里呢?显然 MVC 的 3 大组件根本没有适合放这些代码的地方。</p>
<p>网络请求与数据处理的归属争议:</p>
<ol>
<li>
<p>纯数据模型派:<br>
认为 Model 应仅定义数据结构,网络请求和数据处理应由 <code>Controller</code> 或单独的服务类(如 <code>NetworkManager</code>)处理。</p>
</li>
<li>
<p>增强 Model 派:</p>
<p>支持将网络请求封装在 Model 内部,通过扩展方法或静态函数实现,例如:</p>
<pre><code class="language-objective-c">extension NGLoginModel {
static func fetchAccount(completion: @escaping (NGLoginModel?) -> Void) {
NetworkManager.request(url: "api/login") { data in
let account = NetcallAccount(data: data)
completion(NGLoginModel(info: account))
}
}
}
</code></pre>
<p>这种方式保持数据与获取逻辑的紧密性,但可能增加 Model 的复杂度。</p>
</li>
</ol>
<p>》https://www.jianshu.com/p/309f0477aac1</p>
<h2 id="四-mvp--mvvm">四. MVP / MVVM</h2>
<h3 id="1-mvp--mvvm-解析">1. MVP / MVVM 解析</h3>
<p>这里引用优秀博客,欢迎大家去学习:https://www.jianshu.com/p/b5043499b096</p>
<h3 id="2-mvvm--rxswift">2. MVVM + RxSwift</h3>
<p>这里我用一个示例来说如何使用 MVVM + RxSwift,对于具体 RxSwfit 详细内容这里不做解析。</p>
<p><strong>数据模型</strong>:定义了电影数据结构,遵循 Decodable 协议以便从 JSON 解析。</p>
<pre><code class="language-swift">import Foundation
struct Movie: Decodable {
let title: String
let overview: String
let posterPath: String
let releaseDate: String
enum CodingKeys: String, CodingKey {
case title
case overview
case posterPath = "poster_path"
case releaseDate = "release_date"
}
}
</code></pre>
<p><strong>网络服务层</strong>:使用 Alamofire 进行网络请求,返回 RxSwift 的 Observable 对象。</p>
<pre><code class="language-swift">import RxSwift
import Alamofire
class MovieAPI {
static let shared = MovieAPI()
private let apiKey = "YOUR_API_KEY" // 替换为实际API Key
func fetchPopularMovies() -> Observable<> {
let url = "https://api.themoviedb.org/3/movie/popular"
let parameters: = [
"api_key": apiKey,
"language": "en-US"
]
return Observable.create { observer in
AF.request(url, parameters: parameters)
.validate()
.responseDecodable(of: MovieResponse.self) { response in
switch response.result {
case .success(let movieResponse):
observer.onNext(movieResponse.results)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
struct MovieResponse: Decodable {
let results:
}
</code></pre>
<p><strong>视图模型</strong>:包含业务逻辑,使用 BehaviorRelay 存储数据状态,处理加载和错误状态。</p>
<pre><code class="language-swift">import RxSwift
import Alamofire
class MovieAPI {
static let shared = MovieAPI()
private let apiKey = "YOUR_API_KEY" // 替换为实际API Key
func fetchPopularMovies() -> Observable<> {
let url = "https://api.themoviedb.org/3/movie/popular"
let parameters: = [
"api_key": apiKey,
"language": "en-US"
]
return Observable.create { observer in
AF.request(url, parameters: parameters)
.validate()
.responseDecodable(of: MovieResponse.self) { response in
switch response.result {
case .success(let movieResponse):
observer.onNext(movieResponse.results)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
struct MovieResponse: Decodable {
let results:
}
</code></pre>
<p><strong>视图控制器:</strong>负责 UI 展示,通过 RxSwift 绑定 ViewModel 数据到 UI 控件。</p>
<pre><code class="language-swift">import RxSwift
import Alamofire
class MovieAPI {
static let shared = MovieAPI()
private let apiKey = "YOUR_API_KEY" // 替换为实际API Key
func fetchPopularMovies() -> Observable<> {
let url = "https://api.themoviedb.org/3/movie/popular"
let parameters: = [
"api_key": apiKey,
"language": "en-US"
]
return Observable.create { observer in
AF.request(url, parameters: parameters)
.validate()
.responseDecodable(of: MovieResponse.self) { response in
switch response.result {
case .success(let movieResponse):
observer.onNext(movieResponse.results)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
struct MovieResponse: Decodable {
let results:
}
</code></pre>
<p>这个示例完整展示了 MVVM 架构在 iOS 中的实现,RxSwift 的使用使得数据绑定和异步操作更加简洁高效:</p>
<ul>
<li><strong>Model 层</strong>:Movie 和 MovieAPI 负责数据处理;</li>
<li><strong>ViewModel 层</strong>:MovieListViewModel 处理业务逻辑;</li>
<li><strong>View 层</strong>:MovieListViewController 负责 UI 展示;</li>
</ul><br><br>
来源:https://www.cnblogs.com/hubert-style/p/19092427
頁:
[1]