Osheep

时光不回头,当下最重要。

View层的组织和代码规范

《View层的组织和代码规范》

View代码结构的规定

制定View层规范的重要性在于:

  • 提高业务方View层的可读性可维护性
  • 防止业务代码对架构产生腐蚀
  • 方便后续的迭代和维护

苹果有一套自己的代码规范苹果官方代码规范,ViewController中的代码应该是这样的:

@interface ViewController ()

@property(nonatomic,strong)UIView * topView;

@end

@implementation ViewController

#pragma mark - LifeCycle Menthod
- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

#pragma mark - Delegate Menthod

#pragma mark - Event response

#pragma mark - Private Menthods

#pragma mark - getter && setter
-(UIView*)topView{
    if (!_topView) {
        _topView = [[UIView alloc]init];
    }
    return _topView;
}

@end

要点如下:

所有的属性都是用getter和setter

  1. 不要在viewDidLoad里面初始化view然后再add,这样的代码很难看。
  2. 在viewDidload里面只做addSubView的事情,建议把constraints写在viewDidLoad里面,你可以写一个layoutSubviewConstraints这样的方法,然后在viewDidLoad里面调用它。
  3. 最后在viewDidAppear里面做Notification的监听之类的事情,属性的初始化,则交给getter去做。

比如这样:

#pragma mark - life cycle
- (void)viewDidLoad
{
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.firstTableView];
    [self.view addSubview:self.secondTableView];
    [self.view addSubview:self.firstFilterLabel];
    [self.view addSubview:self.secondFilterLabel];
    [self.view addSubview:self.cleanButton];
    [self.view addSubview:self.originImageView];
    [self.view addSubview:self.processedImageView];
    [self.view addSubview:self.activityIndicator];
    [self.view addSubview:self.takeImageButton];

    //添加约束
    [self layoutSubviewConstraints];
}

- (void)layoutSubviewConstraints
{

    CGFloat width = (self.view.width - 30) / 2.0f;

    self.originImageView.size = CGSizeMake(width, width);
    [self.originImageView topInContainer:70 shouldResize:NO];
    [self.originImageView leftInContainer:10 shouldResize:NO];

    self.processedImageView.size = CGSizeMake(width, width);
    [self.processedImageView right:10 FromView:self.originImageView];
    [self.processedImageView topEqualToView:self.originImageView];

    CGFloat labelWidth = self.view.width - 100;
    self.firstFilterLabel.size = CGSizeMake(labelWidth, 20);
    [self.firstFilterLabel leftInContainer:10 shouldResize:NO];
    [self.firstFilterLabel top:10 FromView:self.originImageView];

    ... ...
}

这样即使在属性非常多的情况下,还能够保持代码整齐,View的初始化都交给getter去做。

getter和setter全部放到最后

因为一个viewcontroller很可能有非常多的view,就像上边的例子一样,如果getter和setter写在前面,就会把主要逻辑扯到后面去,其他人看的时候就先划过一长串的getter和setter,这样不太好,然后要求业务工程师写代码的时候按照顺序来分配代码块的位置,先是life cycle,然后是Delegate方法实现,然后是event response,然后才是getters and setters。这样后来者阅读代码时就能省力很多。

每一个delegate都把对应的protocol带上,delegate方法不要到处乱写,写到一块区域里面去

比如UITableViewDelegate的方法集就老老实实写上#pragma mark – UITableViewDelegate。这样有个好处就是,当其他人阅读一个他并不熟悉的Delegate实现方法时,他只要按住command然后去点这个protocol名字,Xcode就能够立刻跳转到对应这个Delegate的protocol定义的那部分代码去,就省得他到处找了。

event response专门开一个代码区域

所有的button,gestureRecognizer的响应事件都放到这个区域里,不要到处乱放。

关于private menthods,正常情况下viewcontrioller里面不应该写

不是delegate方法的,不是event response方法的,不是life cycle方法的,就是private menthod了。对的,正常情况下viewcontroller里面一般是不会存在private menthods的,这个private menthods一般用于日期换算,图片裁剪的这种小功能,这种小功能要么写成一个category,要么把它做成一个小的模型,哪怕这个模块只有一个函数也行。跟业务关联不大的东西能不放在ViewController里面就不要放


MVC模式

MVC(Model-View-Controller)是比较老的思想,其中model是数据管理者,View作为数据展示者,Controller作为数据加工者,ModelView又是由Controller来根据业务需求调配,所以Controller还负担了一个数据流调配的功能。

为什么我们会纠结于iOS开发领域中MVC的划分问题?

因为UIViewController中自带了一个View,且控制了View的整个生命周期(viewDidLoad,viewWillAppear…),而在常识中我们都知道Controller不应该和View有如此紧密的联系,所以才会对MVC的划分产生困惑。UIViewController中自带的那个view,它的主要任务就是作为一个容器。如果它所有的相关命名改成ViewController,那么代码就会变成这样:

- (void)viewContainerDidLoad
{
    [self.viewContainer addSubview:self.label];
    [self.viewContainer addSubview:self.tableView];
    [self.viewContainer addSubview:self.button];
    [self.viewContainer addSubview:self.textField];
}

iOS客户端的MVC划分,就是这样:

              -----------------------------
               | C                        |
               |   Controller             |
               |           \              |
               |           View Container |
               ----------------------------
              /                            \
             /                              \
            /                                \
------------                                  ----------------------
| M        |                                  | V                  |
|   Model  |                                  |    UITableView     |
|          |                                  |    YourCustomView  |
------------                                  |         ...        |
                                              ----------------------

在iOS开发领域,虽然也有让View去监听事件的做法,但这种做法非常少,都是把事件回传给Cotroller,然后Controller再另行调度,所以这个时候,View的容器放在Controller就非常合适。Controller可以因为不同事件的产生很方便的去改变容器内容,比如加载失败时,把容器内容换成失败页面的View,无网络时,把页面换成无网络的View等等。

M应该做的事:

  • 给ViewController提供数据
  • 给ViewController存储数据提供接口
  • 提供经过抽象的业务基本控件,供Controller调度。

C应该做的事

  • 管理ViewController的生命周期
  • 负责生成所有View的实例,并放入View Container
  • 监听来自View与业务相关的事件,通过与Model合作,来完成对应事件的业务

V应该做的事

  • 响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。
  • 界面元素表达

一个具体的MVC问题

比如一个controller下面有两个tableView,怎样找一个更好的方法去维护这两个tableview

正确的做法应该是:

  • 首先把tableviewdatasource拆分成独立的对象,然后作为controller的property交给controller去调度,加载API的事情也是放在datasource中去做。
  • dataSource只公开一个方法:loadData,如果tableview要翻页,那就在公开一个方法叫loadNextPagedataSource也要设置一个delegate,这个delegate要controller去实现,目的是为了告诉controller API数据的加载进度,因此也会有几个delegate方法:willStartLoadData,didSucessLoadData,didFailedLoadData,这样controller就知道什么时候转菊花什么时候让tableview去reloadData了。

  • tableviewdataSource设置为CustomeDataSource,CustomeDataSource的delegate设置为controller,tableview的delegate也设置为controller

  • controllerviewWillAppear的时候调用self.aDatasource的loaddata方法去发起api请求,然后此dataSource就会调用delegate的willLoadData,也就是controller实现的方法,去转菊花,在dataSource加载数据完毕之后,会回调delegate的didSucess告知controller可以调用self.tableview的reloadData方法了,于是tableview开始向dataSource要数据加载cell,整个流程就完成了。

点赞