MVC / MVP / MVVM 架構解析
認真對待每時、每刻每一件事,把握當下、立即去做。
MVC 模式的目的是實現一種動態的程序設計,使后續對程序的修改和擴展簡化,并且使程序某一部分的重復利用成為可能。除此之外,此模式通過對復雜度的簡化,使程序結構更加直觀。下面主要對 MVC 架構下的優化方案以及其項目結構解析。

一. MVC 相應層應該做什么?
1. 控制器(Controller)業務層
控制器(Controller)-->業務層, Model 與 View 層的中介,負責轉發請求,對請求進行處理,把 Model 數據在 View 上展示出來。
主要職責:
- 管理 View Container 的生命周期;
- 負責生成所有的 View 實例,并放入 View Container;
- 監聽來自 View 與業務有關的事件,通過與 Model 的合作,來完成對應事件的業務;
2. 視圖(View)展現層
視圖(View) -->展現層,承載 UI 展示和事件響應(交互)。
主要職責:
- 響應與業務無關的事件,并因此引發動畫效果,點擊反饋(如果合適的話,盡量還是放在 View 去做)等。
- 界面元素表達;
3. 模型(Model)數據層
模型(Model) -->數據層,數據處理層,包括網絡請求,數據加工,算法實現等。
主要職責:
- 給 ViewController 提供數據;
- 給 ViewController 存儲數據提供接口;
- 提供經過抽象的業務基本組件,供 Controller 調度;

4. 示例解析
在 iOS 中的 Controlller 是 UIViewController,所以導致很多人會把視圖寫在 Controller 中,如下圖:
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//setupUI
//1.createView
UIView *view = [[UIView alloc]init];
view.frame = CGRectMake(100, 100, 100, 100);
view.backgroundColor = [UIColor orangeColor];
[self.view addSubview:view];
//2.createButton
UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoDark];
btn.center = self.view.center;
[self.view addSubview:btn];
//3...
}
這種寫法在我剛學習編程的時候也這樣寫過,先說這樣寫的好處,以及初學者為什么會這么寫:
- 比如按鈕,可以在當前控制器直接
add target:添加點擊事件,在當前控制器內就能調用到點擊方法,不需要設置代理之類的; - 比如要找某個界面,直接切到這個界面對應的
controller就行,因為View寫在Controller里面,不用去別的地方找就這里有; - 比如一個 View,里面有一張圖片,圖片依賴于網絡資源,這樣寫的好處,可以直接讓
View在Controller中就能拿到資源,不需要傳值;
缺點:
- 導致
Controller特別臃腫,里面代碼特別多,視圖一復雜起來,代碼量可能過1000行,不好維護; - 寫在
Controller里無法復用,除非你在 VC2 里面 copy 當前 VC 中的View的代碼; - 特別low!!會被懂架構的人瞧不起,噴你根本不是
MVC,是MC架構;
如何告別 MC 模式,真正走到 MVC?
先給自己洗腦,iOS 的 Controller 不是 UIViewController,而是普通的 Controller,沒有 View。(很關鍵的一步)。
模塊化劃分,每個模塊對應自己的一個 View,例如 Demo 模塊,View 層里面有個 DemoView,將界面元素寫到 View 中。
二. MVC 相應層之間如何通信?

1. View 層和 Controller 層雙向通信
1.1 Controller 如何將數據傳遞到 View 層
- 創建 View 的時候通過 View 的函數作為外部參數傳進去。
1.2 View 層(用戶事件)如何傳遞到 Controller 層
1.2.1 代理(delegate)
通過代理(delegate),代理委托模式通過定義協議方法實現解耦, View 只關心事件觸發不處理具體邏輯;
// 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 {
[self.delegate customView:self didTapButton:sender]; // 觸發代理方法
}
@end
// 3. Controller 實現協議
@interface ViewController () <CustomViewDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
CustomView *view = [[CustomView alloc] init];
view.delegate = self; // 設置代理
}
- (void)customView:(CustomView *)view didTapButton:(UIButton *)button {
NSLog(@"Delegate: 按鈕點擊事件處理"); // Controller 響應事件
}
@end
1.2.2 target-action 監聽
在 Controller 設置 target-action 監聽,Controller 給 View 添加一個 target,當用戶的觸摸事件發生時,view 產生 action,Controller 接收到之后做出相應的響應,直接建立 View 與控制器的響應鏈關系,適合簡單控件事件;
// 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"
[_target performSelector:_action withObject:self]; // 執行 Action
#pragma clang diagnostic pop
}
@end
// 2. Controller 設置 Target-Action
@implementation ViewController
- (void)viewDidLoad {
CustomView *view = [[CustomView alloc] init];
[view addTarget:self action:@selector(handleButtonTap:)]; // 綁定事件
}
- (void)handleButtonTap:(CustomView *)sender {
NSLog(@"Target-Action: 按鈕點擊事件處理"); // Controller 響應事件
}
@end
1.2.3 數據源模式 data source
通過數據源模式 data source,通過數據驅動 UI 更新,控制器實現數據獲取協議供 View 調用;
// 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 = [self.dataSource textForButtonInView:self]; // 獲取數據
[_button setTitle:text forState:UIControlStateNormal];
}
@end
// 3. Controller 實現數據源
@interface ViewController () <CustomViewDataSource>
@end
@implementation ViewController
- (void)viewDidLoad {
CustomView *view = [[CustomView alloc] init];
view.dataSource = self;
[view reloadData]; // 初始化數據
}
- (NSString *)textForButtonInView:(CustomView *)view {
return @"DataSource 模式"; // 提供動態數據
}
@end
1.2.4 Block(閉包)
Block(閉包):?View 定義閉包屬性,Controller 通過賦值閉包來響應事件。?優點,代碼緊湊,適合簡單回調。?缺點,需注意循環引用(使用 [weak self])。
class CustomView: UIView {
var onButtonTap: (() -> Void)?
@objc func buttonTapped() { onButtonTap?() }
}
// Controller 中賦值
customView.onButtonTap = { [weak self] in self?.handleTap() }
2. Model 層和 Controller 層雙向通信
我們來看下這里的 Model 層通信,先看一段代碼。
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//刷新tableView
_datas = responseObject;
[_tableView reloadDatas];
} failure:nil];
}
這種寫法在我剛學習編程的時候也這樣寫過,先說這樣寫的好處,以及初學者為什么會這么寫:
- 簡單,網絡請求完,直接在當前控制器刷新
TableView的數據源; - 比如要找某個界面的網絡請求,直接切到這個界面對應的
controller就行,因為數據請求 寫在Controller里面,不用去別的地方找,就這里有; - 比如當前網絡請求接口,需要外部參數,比如前一個界面的
uuid,這樣寫的好處,可以直接讓當前請求在Controller中就能拿到資源,不需要傳值;
缺點:
- 又導致
Controller特別臃腫,里面代碼特別多,如果當前控制器需要多次請求,代碼量可能過1000行,不好維護; - 寫在
Controller里無法復用,除非你在 VC2 里面 copy 當前 VC 中的網絡請求的代碼; - 如果某些接口有依賴要求,接口1請求完再請求接口2,需要嵌套起來;
- 特別 low!!會被懂架構的人瞧不起,噴你根本不是
MVC,如果你還用了上面的View寫在Controller的操作的話,恭喜你,最終大法 -Controller 架構順利完成,并不需要什么Model&&View;
這 iOS 的 Controller 就算是 UIViewController,也沒看到 Model 啊,沒有 Model。(很關鍵的一步);
模塊化劃分,每個模塊對應自己的一個 Model,例如 Demo 模塊,Model 層里面有個 DemoModel,將網絡請求&&數據處理寫到 Model 中;
2.1 Controller 調用和傳值到 Model
Controller 層直接調用 Model 層類方法和實例方法,并通過參數傳值。
2.2 Model 層數據如何回調到 Controller 層
Model 層數據如何回調到 Controller 層,Controller 層如何知道 Model 層數據發生了改變。
2.2.1 Block 回調
輕量級單向通信,適合簡單回調但需注意循環引用
//Model
@implementation DemoModel
+ (void)fetchDatasWithUUid:(NSString *)uuid success:(successBlock)block{
//Model發送網絡請求
NSDictionary *parameters = @{@"uuid":uuid}
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//通過block異步回調~
block(responseObject);
} failure:nil];
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas
[DemoModel fetchDatasWithUUid:_uuid success:^(NSArray *array) {
_datas = array;
[_tableView reloadDatas];
}];
}
2.2.2 KVO(監聽)
KVO(監聽),監聽 Model 的每個屬性的變化來做出響應;
// Model.h
@interface MyModel : NSObject
@property (nonatomic, strong) NSString *data;
@end
// Controller.m
- (void)viewDidLoad {
[super 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 ([keyPath isEqualToString:@"data"]) {
self.label.text = change[NSKeyValueChangeNewKey]; // 響應變化
}
}
- (void)dealloc {
[self.model removeObserver:self forKeyPath:@"data"];
}
2.2.3 Notification(通知)
Notification(通知),Model 中創建一個 NSNotificationCenter,在 Controller 中創建一個方法來接收通知。當 Model 發生變化時,他會發送一個通知,而 Controller 會接收通知,一對多廣播式通信,適合跨模塊解耦但性能開銷較大。

解釋一下上面這幅圖,一個完整的模塊被分為了三個相對獨立的部分,分別是Model,View,Controller,對應到我們 App 中的依次為繼承自 NSObject 的數據中心,承載 UI 展示和事件響應的 View 以及我們最最常用的 UIViewController。
其中 VC 持有 View 和 Model 部分,View 通過代理或者 Target-Action 的方式把用戶的操作傳遞給 VC,VC 負責根據不同的用戶行為做出不同響應。如果需要加載或刷新數據則直接調用 Model 暴露的接口,如果數據可以同步拿到,則直接使用獲取到的數據刷新 View。如果數據需要通過網絡請求等其他異步的方式獲取,VC 則通過監聽 Model 發出的數據更新(成功或失敗)通知,在收到通知時根據成功或者失敗對 View 進行相應的刷新操作。可以看出來整個過程中 View 和 Model 是沒有直接交互的,所有的操作都是通過 VC 進行協調的。
基礎的 MVC 講解完畢,其實本質上就是讓 Controller 減壓,不該控制器管的他別讓他知道,如上基礎 MVC 操作之后的優勢:
- MVC 架構分明,在同一個模塊內,如果視圖有問題,找到該模塊的
View就行,其他同理,Controller代碼大大減少,負責View的代理事件就可以; - 可以復用,比如你一個產品列表的數據,首頁也要用,產品頁也要用,直接分別在其對應的
VC1&&VC2調用函數[ProductModel fetchDatas]即可,無需寫多次,View 的復用同理; - 結構分明,便于維護,拓展也是在此基礎上拓展,代碼干凈簡潔。
三. MVC 架構常見的疑惑
1. 遺失的網絡邏輯(網絡數據請求應該放在那里?)
蘋果使用的 MVC 的定義是這么說的:所有的對象都可以被歸類為一個 Model,一個 View,或是一個控制器。就這些,那么把網絡代碼放哪里?和一個 API 通信的代碼應該放在哪兒?
你可能試著把它放在 Model 對象里,但是也會很棘手,因為網絡調用應該使用異步,這樣如果一個網絡請求比持有它的 Model 生命周期更長,事情將變的復雜。顯然也不應該把網絡代碼放在 View 里,因此只剩下控制器了。這同樣是個壞主意,因為這加劇了厚重控制器的問題。那么應該放在那里呢?顯然 MVC 的 3 大組件根本沒有適合放這些代碼的地方。
網絡請求與數據處理的歸屬爭議:
-
?純數據模型派:
認為 Model 應僅定義數據結構,網絡請求和數據處理應由Controller或單獨的服務類(如NetworkManager)處理。 -
?增強 Model 派:
支持將網絡請求封裝在 Model 內部,通過擴展方法或靜態函數實現,例如:
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)) } } }這種方式保持數據與獲取邏輯的緊密性,但可能增加 Model 的復雜度。
》https://www.jianshu.com/p/309f0477aac1
四. MVP / MVVM
1. MVP / MVVM 解析
這里引用優秀博客,歡迎大家去學習:https://www.jianshu.com/p/b5043499b096
2. MVVM + RxSwift
這里我用一個示例來說如何使用 MVVM + RxSwift,對于具體 RxSwfit 詳細內容這里不做解析。
數據模型?:定義了電影數據結構,遵循 Decodable 協議以便從 JSON 解析。
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"
}
}
網絡服務層?:使用 Alamofire 進行網絡請求,返回 RxSwift 的 Observable 對象。
import RxSwift
import Alamofire
class MovieAPI {
static let shared = MovieAPI()
private let apiKey = "YOUR_API_KEY" // 替換為實際API Key
func fetchPopularMovies() -> Observable<[Movie]> {
let url = "https://api.themoviedb.org/3/movie/popular"
let parameters: [String: Any] = [
"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: [Movie]
}
視圖模型?:包含業務邏輯,使用 BehaviorRelay 存儲數據狀態,處理加載和錯誤狀態。
import RxSwift
import Alamofire
class MovieAPI {
static let shared = MovieAPI()
private let apiKey = "YOUR_API_KEY" // 替換為實際API Key
func fetchPopularMovies() -> Observable<[Movie]> {
let url = "https://api.themoviedb.org/3/movie/popular"
let parameters: [String: Any] = [
"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: [Movie]
}
?視圖控制器:負責 UI 展示,通過 RxSwift 綁定 ViewModel 數據到 UI 控件。
import RxSwift
import Alamofire
class MovieAPI {
static let shared = MovieAPI()
private let apiKey = "YOUR_API_KEY" // 替換為實際API Key
func fetchPopularMovies() -> Observable<[Movie]> {
let url = "https://api.themoviedb.org/3/movie/popular"
let parameters: [String: Any] = [
"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: [Movie]
}
這個示例完整展示了 MVVM 架構在 iOS 中的實現,RxSwift 的使用使得數據綁定和異步操作更加簡潔高效:
- ?Model 層?:Movie 和 MovieAPI 負責數據處理;
- ?ViewModel 層?:MovieListViewModel 處理業務邏輯;
- ?View 層?:MovieListViewController 負責 UI 展示;

浙公網安備 33010602011771號