《谈谈 MVX 中的 Controller 》笔记 | 青训营笔记

204 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第4天

谈谈 MVX 中的 Controller - 面向信仰编程

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 变得更加复杂了

建议

  1. 不要把DataSource提取出来

    只是简单地将试图控制器中的一部分代码移到了别的位置,因为增加了额外的类是的Controller维护变得更加复杂

  2. 是否要惰性初始化

    两者是等价的,作者更倾向于使用后者。关键是在同一个项目中坚持使用同一种写法

Untitled.png

  1. 将Model相关的业务逻辑都放到Model层中,从而减轻Controller的负担

  2. 将视图层代码移到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
    
  3. 使用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的

stackoverflow.com/questions/1…