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
- 不要在viewDidLoad里面初始化view然后再add,这样的代码很难看。
- 在viewDidload里面只做addSubView的事情,建议把constraints写在viewDidLoad里面,你可以写一个layoutSubviewConstraints这样的方法,然后在viewDidLoad里面调用它。
- 最后在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
作为数据加工者,Model
和View
又是由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
?
正确的做法应该是:
-
首先把
tableview
的datasource
拆分成独立的对象,然后作为controller
的property交给controller
去调度,加载API的事情也是放在datasource
中去做。 -
dataSource
只公开一个方法:loadData
,如果tableview
要翻页,那就在公开一个方法叫loadNextPage
,dataSource
也要设置一个delegate,这个delegate要controller
去实现,目的是为了告诉controller API数据的加载进度,因此也会有几个delegate方法:willStartLoadData,didSucessLoadData,didFailedLoadData,这样controller就知道什么时候转菊花什么时候让tableview去reloadData了。 -
tableview
的dataSource
设置为CustomeDataSource,CustomeDataSource的delegate设置为controller
,tableview的delegate也设置为controller
。 -
当
controller
在viewWillAppear
的时候调用self.aDatasource的loaddata
方法去发起api请求,然后此dataSource就会调用delegate的willLoadData
,也就是controller
实现的方法,去转菊花,在dataSource加载数据完毕之后,会回调delegate的didSucess告知controller
可以调用self.tableview的reloadData方法了,于是tableview
开始向dataSource要数据加载cell,整个流程就完成了。