iOS开发基础126-深入探索设计模式
在iOS开发中,主要的设计模式包括MVC(Model-View-Controller)、MVVM(Model-View-ViewModel)和MVP(Model-View-Presenter)。这些模式旨在分离关注点,简化代码维护和提高代码的可测试性。实际项目中,选择模式取决于项目复杂度、团队习惯、以及具体需求。下面我们详细解析每种模式,并给出示例说明。
优缺点对比
-
MVC
- 优点:简单、易于理解和实现。
- 缺点:当应用变得复杂时,Controller 可能会变得臃肿,难以维护。
-
MVVM
- 优点:解耦了 View 和 Model,ViewModel 更易于测试;数据绑定简化了 UI 更新工作。
- 缺点:需要处理数据绑定相关的复杂性,尤其在 Objective-C 中实现数据绑定(相较于 Swift)会更繁琐。
-
MVP
- 优点:Presenter 中心化业务逻辑,使得 View 更加简洁;易于测试 Presenter。
- 缺点:View 和 Presenter 的关系更加紧密,可能会导致一些重复代码。
下面将更详细地探讨每种模式的深层次特点、优缺点和应用场景,并补充一些实际项目中的使用注意事项和技巧。
一、MVC(Model-View-Controller)
1. 深入理解
MVC 是一种将应用程序分为三部分的方法,以分离内部表示、用户交互和具体实现方式。以下是 MVC 的一些具体特点:
-
Model:
- 负责应用程序的数据逻辑。
- 独立于用户界面。
- 可以是简单的对象,包含属性和计算逻辑。
-
View:
- 负责显示 Model 的数据。
- 独立于业务逻辑,不应包含任何数据处理逻辑。
- 响应用户的输入,通常将这些输入转发给 Controller。
-
Controller:
- 负责接收用户的输入,并调用 Model 和 View 执行响应动作。
- 在整个架构中发挥桥梁作用,协调 Model 和 View 的交互。
2. 示例
Model
@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
@implementation User
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = name;
_age = age;
}
return self;
}
@end
View
@interface UserView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
- (void)updateWithUser:(User *)user;
@end
@implementation UserView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
_ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
[self addSubview:_nameLabel];
[self addSubview:_ageLabel];
}
return self;
}
- (void)updateWithUser:(User *)user {
self.nameLabel.text = user.name;
self.ageLabel.text = [NSString stringWithFormat:@"%@", user.age];
}
@end
Controller
@interface UserController : UIViewController
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) User *user;
@end
@implementation UserController
- (void)viewDidLoad {
[super viewDidLoad];
self.user = [[User alloc] initWithName:@"John Doe" age:30];
self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.userView];
[self.userView updateWithUser:self.user];
}
@end
3. 优缺点
- 优点:直观,容易理解和实现;遵循单一责任原则(每个部分有明确职责)。
- 缺点:Controller 容易变得臃肿,特别是在复杂的应用程序中。
4. 使用注意事项
- 避免在 Controller 中添加过多逻辑,尽量将业务逻辑放到 Model 中处理。
- View 应尽可能简单,避免包含业务逻辑。
二、MVVM(Model-View-ViewModel)
1. 深入理解
MVVM 通过引入 ViewModel 层,进一步解耦视图和业务逻辑。其核心思想是由 ViewModel 处理 View 的所有状态、数据和行为。
- Model:与 MVC 中一样,负责数据和业务逻辑。
- View:负责界面和 UI 逻辑。
- ViewModel:是 Model 和 View 的中介,处理所有的界面逻辑,使得 View 更加简单。
2. 示例
Model
@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
@implementation User
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = name;
_age = age;
}
return self;
}
@end
View
@interface UserView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
@end
@implementation UserView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
_ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
[self addSubview:_nameLabel];
[self addSubview:_ageLabel];
}
return self;
}
@end
ViewModel
@interface UserViewModel : NSObject
@property (nonatomic, strong) User *user;
@property (nonatomic, strong, readonly) NSString *displayName;
@property (nonatomic, strong, readonly) NSString *displayAge;
- (instancetype)initWithUser:(User *)user;
- (void)updateWithUser:(User *)user;
@end
@implementation UserViewModel
- (instancetype)initWithUser:(User *)user {
self = [super init];
if (self) {
[self updateWithUser:user];
}
return self;
}
- (void)updateWithUser:(User *)user {
self.user = user;
}
- (NSString *)displayName {
return self.user.name;
}
- (NSString *)displayAge {
return [NSString stringWithFormat:@"%@", self.user.age];
}
@end
ViewController
@interface UserViewController : UIViewController
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) UserViewModel *viewModel;
@end
@implementation UserViewController
- (void)viewDidLoad {
[super viewDidLoad];
User *user = [[User alloc] initWithName:@"John Doe" age:30];
self.viewModel = [[UserViewModel alloc] initWithUser:user];
self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.userView];
[self updateView];
}
- (void)updateView {
self.userView.nameLabel.text = self.viewModel.displayName;
self.userView.ageLabel.text = self.viewModel.displayAge;
}
@end
3. 优缺点
- 优点:通过数据绑定简化 View;解耦 View 和 Model,提高代码可测试性和复用性。
- 缺点:实施数据绑定(特别是 Objective-C 中的 KVO)可能复杂且容易出错;对于简单项目,可能有过度设计的风险。
4. 使用注意事项
- ViewModel 中不应包含 UIKit 相关的代码,应保持纯粹的数据处理和逻辑操作。
- 数据绑定需要仔细设计,避免内存泄漏和循环引用。
三、结合 RAC 的 MVVM
ReactiveCocoa(简称 RAC) 是一个用于实现响应式编程的框架。RAC 可以用于简化事件处理、数据绑定,并更好地支持 MVVM 模式。在结合 RAC 的实践中,以下场景非常适用:
- 数据绑定:将 View 和 ViewModel 绑定,使得 View 自动反应数据变化。
- 流式处理:处理复杂的异步事件和操作。
- 避免 callback:通过信号和观察者机制减少回调地狱。
通过结合 RAC 和 MVVM 模式,可以大幅度简化代码并提高响应能力。
还不知道怎么使用RAC的同学戳这iOS开发基础111-RAC
1. 实现步骤
- Model:定义
User
模型。 - View:定义展示用户信息的视图。
- ViewModel:处理业务逻辑及响应数据变化。
- 数据绑定:将 View 和 ViewModel 绑定。
2. 示例代码
Model
// User.h
@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
// User.m
@implementation User
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = name;
_age = age;
}
return self;
}
@end
View
// UserView.h
@interface UserView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
@end
// UserView.m
@implementation UserView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
_ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
[self addSubview:_nameLabel];
[self addSubview:_ageLabel];
}
return self;
}
@end
ViewModel
// UserViewModel.h
#import <ReactiveObjC/ReactiveObjC.h>
#import "User.h"
@interface UserViewModel : NSObject
@property (nonatomic, strong, readonly) RACSignal *nameSignal;
@property (nonatomic, strong, readonly) RACSignal *ageSignal;
- (instancetype)initWithUser:(User *)user;
@end
// UserViewModel.m
#import "UserViewModel.h"
@interface UserViewModel ()
@property (nonatomic, strong) User *user;
@end
@implementation UserViewModel
- (instancetype)initWithUser:(User *)user {
self = [super init];
if (self) {
_user = user;
// 创建数据信号
_nameSignal = RACObserve(self, user.name);
_ageSignal = [RACObserve(self, user.age) map:^id _Nullable(id _Nullable value) {
return [NSString stringWithFormat:@"Age: %@", value];
}];
}
return self;
}
@end
ViewController
// UserViewController.h
@interface UserViewController : UIViewController
@end
// UserViewController.m
#import "UserViewController.h"
#import "UserView.h"
#import "UserViewModel.h"
#import "User.h"
@interface UserViewController ()
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) UserViewModel *viewModel;
@end
@implementation UserViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建用户和 ViewModel
User *user = [[User alloc] initWithName:@"John Doe" age:@30];
self.viewModel = [[UserViewModel alloc] initWithUser:user];
// 设置 UserView
self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.userView];
// 数据绑定
[self bindViewModel];
}
- (void)bindViewModel {
RAC(self.userView.nameLabel, text) = self.viewModel.nameSignal;
RAC(self.userView.ageLabel, text) = self.viewModel.ageSignal;
}
@end
3、优缺点
优点:
- 代码更简洁:通过信号和数据绑定,减少繁琐的代码。
- 提升响应能力:View 自动响应 ViewModel 的变化。
- 提高可测试性:通过独立的 ViewModel 层,业务逻辑可以相对独立地进行测试。
缺点:
- 学习曲线:RAC 和响应式编程的概念较为复杂,需要开发者理解信号、观察者等基础知识。
- 调试复杂:因为涉及到信号和订阅,调试过程可能比传统代码复杂。
4、总结
结合了 ReactiveCocoa 的 MVVM 使得数据绑定和事件处理变得简单和优雅,通过信号和响应式编程提升了代码的可读性和可维护性。
四、MVP(Model-View-Presenter)
1. 深入理解
MVP 模式通过引入 Presenter,完全分离了视图和业务逻辑。Presenter 负责处理所有从 View 接收的用户交互,并协调 Model 和 View 之间的通信。
- Model:负责数据和业务逻辑。
- View:通常是协议,定义更新 UI 的方法。
- Presenter:负责处理来自 View 的用户输入,并更新 View。
2. 示例
Model
@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
@implementation User
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = name;
_age = age;
}
return self;
}
@end
View
@protocol UserViewProtocol <NSObject>
- (void)updateName:(NSString *)name;
- (void)updateAge:(NSString *)age;
@end
@interface UserView : UIView <UserViewProtocol>
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
@end
@implementation UserView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
_ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
[self addSubview:_nameLabel];
[self addSubview:_ageLabel];
}
return self;
}
- (void)updateName:(NSString *)name {
self.nameLabel.text = name;
}
- (void)updateAge:(NSString *)age {
self.ageLabel.text = age;
}
@end
Presenter
@interface UserPresenter : NSObject
@property (nonatomic, strong) User *user;
@property (nonatomic, weak) id<UserViewProtocol> view;
- (instancetype)initWithUser:(User *)user view:(id<UserViewProtocol>)view;
- (void)updateUser;
@end
@implementation UserPresenter
- (instancetype)initWithUser:(User *)user view:(id<UserViewProtocol>)view {
self = [super init];
if (self) {
_user = user;
_view = view;
}
return self;
}
- (void)updateUser {
[self.view updateName:self.user.name];
[self.view updateAge:[NSString stringWithFormat:@"%@", self.user.age]];
}
@end
ViewController
@interface UserViewController : UIViewController <UserViewProtocol>
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) UserPresenter *presenter;
@end
@implementation UserViewController
- (void)viewDidLoad {
[super viewDidLoad];
User *user = [[User alloc] initWithName:@"John Doe" age:30];
self.presenter = [[UserPresenter alloc] initWithUser:user view:self];
self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.userView];
[self.presenter updateUser];
}
// UserViewProtocol methods
- (void)updateName:(NSString *)name {
[self.userView updateName:name];
}
- (void)updateAge:(NSString *)age {
[self.userView updateAge:age];
}
@end
3. 优缺点
- 优点:完全分离视图和业务逻辑,使 View 非常轻量;提高代码的可测试性。
- 缺点:需要定义大量协议和接口,增加了代码的复杂性。
4. 使用注意事项
- View 协议应尽量简单,只包含必要的 UI 更新方法。
- Presenter 中不应直接引用 UIKit 相关代码,保持其纯粹的逻辑角色。
总结
深入理解 MVC、MVVM 和 MVP,可以帮助开发者选择最适合的架构模式进行开发。不同模式适用于不同的复杂度和需求的项目,理解每种模式的优缺点和适用场景是关键。无论选择哪种模式,都应遵循单一责任原则,保持代码的可维护性和可测试性。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2015-07-17 iOS开发基础10-UIButton内边距和图片拉伸模式
2015-07-17 iOS开发基础9-提示框(UIAlertController)