这是我参与「第四届青训营 」笔记创作活动的第4天
Controller层的对象都是UIVIewController的实例
Controller负责的功能
- 管理根视图的生命周期和应用生命周期
- 负责将视图层的UIView对象添加到持有的根视图上
- 负责处理用户行为,比如UIButton的点击以及手势的触发
- 存储当前界面的状态
- 处理界面之间的跳转
- 作为UITableView以及其他容器视图的代理以及数据源
- 负责HTTP请求的发起
每一个UIViewController都持有一个UIView对象,视图层无法脱离UIViewController单独存在,必须成为这个UIView的子视图
🤔 在Controller中对View进行初始化和布局是避免不了的
- (void)setupUI {
DRKBackgroundView *backgroundView = [[DRKBackgroundView alloc] init];
[backgroundView.registerButton addTarget:self action:@selector(registerButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:backgroundView];
[backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
}
使用惰性初始化
- (UIImageView *)backgroundView {
if (!_backgroundView) {
_backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"backgroundView"]];
}
return _backgroundView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.backgroundView];
[self.backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
}
惰性初始化没有绝对的优劣,作者的建议是一个项目里坚持一种做法。
Controller是可以存储当前页面的状态的,比如存储一个model的数组。
关于页面的跳转,iOS总共有三种方式
UINavigationController中使用 push 和 pop 改变栈顶的UIViewController对象;UITabBarController中点击各个UITabBarItem实现跳转;- 使用所有的
UIViewController实例都具有的presentViewController:animated:completion方法;
作者提到,很多文章中都提供了一种用于减少 Controller 层中代理方法数量的技巧,就是使用一个单独的类作为 UITableView 或者其他视图的代理:
self.tableView.delegate = anotherObject;
self.tableView.dataSource = anotherObject;
但他并不认为这样有什么太大的用处
只是将代理方法挪到了一个其他的地方,如果这个代理方法还依赖于当前
UIViewController实例的上下文,还要向这个对象中传入更多的对象,反而让原有的 MVC 变得更加复杂了
建议
-
不要把DataSource提取出来
只是简单地将试图控制器中的一部分代码移到了别的位置,因为增加了额外的类是的Controller维护变得更加复杂
-
是否要惰性初始化
两者是等价的,作者更倾向于使用后者。关键是在同一个项目中坚持使用同一种写法
-
将Model相关的业务逻辑都放到Model层中,从而减轻Controller的负担
-
将视图层代码移到View层
作者觉得如果View比较简单,直接在Controller中写View也无可厚非,但如果视图层的对象非常多,应该将视图层的代码移到单独的UIView子类中
// RegisterView.h @interface RegisterView : UIView @property (nonatomic, strong) UITextField *phoneNumberTextField; @property (nonatomic, strong) UITextField *passwordTextField; @end // RegisterView.m @implementation RegisterView // 用initWithFrame初始化 - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self addSubview:self.phoneNumberTextField]; [self addSubview:self.passwordTextField]; [self.phoneNumberTextField mas_makeConstraints:^(MASConstraintMaker *make) { ... }]; [self.passwordTextField mas_makeConstraints:^(MASConstraintMaker *make) { ... }]; } return self; } // 惰性初始化 - (UITextField *)phoneNumberTextField { if (!_phoneNumberTextField) { _phoneNumberTextField = [[UITextField alloc] init]; _phoneNumberTextField.font = [UIFont systemFontOfSize:16]; } return _phoneNumberTextField; } - (UITextField *)passwordTextField { if (!_passwordTextField) { _passwordTextField = [[UITextField alloc] init]; ... } return _passwordTextField; } @end🤔 作者这里提到让Controller持有这个视图的做法是将自己持有的根视图替换成该视图对象。
在UIViewController中可以复写loadView方法改变其本身持有的视图对象,注意在这里Frame会被初始化成屏幕大小
@interface ViewController () @property (nonatomic, strong) RegisterView *view; @end @implementation ViewController @dynamic view; // 在loadView中进行替换 - (void)loadView { // 要初始化这个View self.view = [[RegisterView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; } - (void)viewDidLoad { [super viewDidLoad]; } @end -
使用pragma分割代码块,swift中用extension
作者把一个UIViewController分割为如下几个部分
- 生命周期以及一些需要
override的方法 - 视图层代码的初始化
- 各种数据源和代理协议的实现
- 事件、手势和通知的回调
- 实例变量的存取方法
- 一些其他的 Helper 方法
- 生命周期以及一些需要
#pragma mark - UI
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - Callback
#pragma mark - Getter/Setter
#pragma mark - Helper
最后,关于View中能否拥有Model,作者表示这样会增加Model和View的耦合性,而下面这篇文章的讨论中表示还是要看情况,View中是可以拥有Model的