在 iOS 开发中,随着应用规模增大和业务复杂度提升,单纯的 MVC 很容易导致 ViewController 臃肿、难以维护。本文通过示例,系统讲解 MVVM 和 VIPER 架构,并分析如何选择合适架构。
一、MVVM vs MVC
-
MVC 的问题
-
Controller 承担了 UI 展示 + 业务逻辑 + 数据处理 + 网络请求
-
随着项目复杂度增加,Controller 越来越臃肿(Massive ViewController 问题)
-
-
MVVM 的改进
-
ViewModel 负责 数据加工 + 状态管理 + 业务逻辑
-
View/Controller 只负责 UI 展示和用户交互
-
优点:
-
Controller 变轻
-
数据和 UI 分离
二、MVVM 架构示例
Model
@interface EVUser : NSObject
@property (nonatomic, copy) NSString *userID;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger gender; // 0 女,1 男
@end
@interface EVUserDisplayItem : NSObject
@property (nonatomic, copy) NSString *userID;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *genderString;
@end
ViewModel
@interface EVUserListViewModel : NSObject
@property (nonatomic, copy) NSArray<EVUserDisplayItem *> *displayItems;
- (void)loadUsers;
@end
@implementation EVUserListViewModel
- (void)loadUsers {
EVUser *user1 = [EVUser new]; user1.userID = @"1"; user1.name = @"Evan"; user1.gender = 1;
EVUser *user2 = [EVUser new]; user2.userID = @"2"; user2.name = @"Alice"; user2.gender = 0;
NSMutableArray *items = [NSMutableArray array];
for (EVUser *user in @[user1, user2]) {
EVUserDisplayItem *item = [EVUserDisplayItem new];
item.userID = user.userID;
item.name = user.name;
item.genderString = (user.gender == 0) ? @"女" : @"男";
[items addObject:item];
}
self.displayItems = items;
}
@end
三、VIPER 对 MVVM 的进一步解耦
MVVM 已经把 Controller 和数据逻辑分开,但:
-
ViewModel 可能还直接依赖 Service/网络请求
-
模块之间耦合仍存在
VIPER 做法:
-
通过协议完全解耦模块:View ↔ Presenter ↔ Interactor ↔ Router
-
职责更细化:
-
Interactor:业务逻辑、网络请求
-
Presenter:把业务数据转换成 UI 可展示格式
-
Router:页面跳转 / 对外接口
-
View:纯 UI 展示
-
一句话总结:
-
MVVM → 解决 Controller 臃肿问题,让数据和 UI 分离
-
VIPER → 在 MVVM 基础上,把各模块完全解耦,明确职责,并通过 Router 暴露统一入口
四、VIPER 核心示例
Entity
@interface EVVIPERUser : NSObject
@property (nonatomic, copy) NSString *userID;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger gender;
@end
@interface EVVIPERUserDisplayItem : NSObject
@property (nonatomic, copy) NSString *userID;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *genderString;
@end
Interactor
@protocol EVVIPERUserListInteractorProtocol <NSObject>
- (void)fetchUsersWithCompletion:(void(^)(NSArray<EVVIPERUser *> *users))completion;
@end
@interface EVVIPERUserListInteractor : NSObject<EVVIPERUserListInteractorProtocol>
@end
@implementation EVVIPERUserListInteractor
- (void)fetchUsersWithCompletion:(void(^)(NSArray<EVVIPERUser *> *users))completion {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
EVVIPERUser *user1 = [EVVIPERUser new]; user1.userID = @"1"; user1.name = @"Evan"; user1.gender = 1;
EVVIPERUser *user2 = [EVVIPERUser new]; user2.userID = @"2"; user2.name = @"Alice"; user2.gender = 0;
if (completion) completion(@[user1, user2]);
});
}
@end
Presenter
@protocol EVVIPERUserListViewProtocol <NSObject>
- (void)updateWithDisplayItems:(NSArray<EVVIPERUserDisplayItem *> *)items;
@end
@protocol EVVIPERUserListPresenterProtocol <NSObject>
- (void)loadUsers;
@end
@interface EVVIPERUserListPresenter : NSObject <EVVIPERUserListPresenterProtocol>
@property (nonatomic, weak) id<EVVIPERUserListViewProtocol> view;
@property (nonatomic, strong) id<EVVIPERUserListInteractorProtocol> interactor;
@property (nonatomic, strong) id router;
@end
@implementation EVVIPERUserListPresenter
- (void)loadUsers {
[self.interactor fetchUsersWithCompletion:^(NSArray<EVVIPERUser *> *users) {
NSMutableArray *items = [NSMutableArray array];
for (EVVIPERUser *user in users) {
EVVIPERUserDisplayItem *item = [EVVIPERUserDisplayItem new];
item.userID = user.userID;
item.name = user.name;
item.genderString = (user.gender == 0) ? @"女" : @"男";
[items addObject:item];
}
[self.view updateWithDisplayItems:items];
}];
}
@end
五、MVVM 与 VIPER 对比
| 特性 | MVVM | VIPER |
|---|---|---|
| 关注点 | 数据驱动、界面更新 | 模块化、业务解耦 |
| 文件数量 | 较少 | 较多 |
| 测试难度 | 中等 | 中等 |
| 适用场景 | 小中型项目 | 中大型项目,复杂业务逻辑 / SDK |
| 学习成本 | 低 | 高 |
| 数据处理 | ViewModel | Interactor + Presenter |
| 事件处理 | Controller | View + Presenter |
| 模块替换性 | 中 | 高 |
六、补充实践点
-
Service 层:处理网络请求、接口封装、模拟数据等,不影响 ViewModel 逻辑
-
异步请求:ViewModel 调用 Service,获取数据后回调更新 View
-
点击事件 / 埋点:一般在 View 或 Presenter 处理,保持 ViewModel 纯数据处理











网友评论