如何把View Controller瘦下来
- 有时候View Controller由于做了太多的事情,而变得非常庞大。这里既有数据的收集,又有逻辑的处理,还有各种归属于该View Controller的控件内存分配。这里面哪些可以代理到其他模块呢?这篇博客就是探索项目的架构,目的是分离复杂的代码逻辑,让我们的代码可读性更强。
- 在View Controller里,这些职责或许是被各种
#pragma mark分组实现,如果你是这样的话,那么就可以考虑把这些部分拆分到不同的文件里。
Data Source
- Data Source的方式是一种拆离View Controller里数据显示逻辑的方式,尤其是在一些复杂的table views里,这种方式可以有效地从View Controller里分离所有cells的数据显示逻辑。
Data Source对象可以遵守
UITableViewDataSource协议,以实现数据的显示,但是我发现使用这些对象配置cells是一件可以独立出来的逻辑,所以可以把这部分逻辑也独立出来。下面一个很简单的例子:@implementation TBSectionedDataSource : NSObject - (instancetype)initWithObjects:(NSArray *)objects sectioningKey:(NSString *)sectioningKey { self = [super init]; if (!self) return nil; [self sectionObjects:objects withKey:sectioningKey]; return self; } - (void)sectionObjects:(NSArray *)objects withKey:(NSString *)sectioningKey { self.sectionedObjects = objects //section the objects array } - (NSUInteger)numberOfSections { return self.sectionedObjects.count; } - (NSUInteger)numberOfObjectsInSection:(NSUInteger)section { return [self.sectionedObjects[section] count]; } - (id)objectAtIndexPath:(NSIndexPath *)indexPath { return self.sectionedObjects[indexPath.section][indexPath.row]; } @end这种data source的设计是为了抽象和重用,不要担心你的类仅仅在一个地方使用。从view controller里分离数据显示逻辑是一种管理懒加载的方式。特别是针对一个动态table views来说,这种方式很适合view controller来管理显示数据。
- 这种方式也可以管理你的重用逻辑。在这里可以获取服务器端的数据,从而把网络访问模块给分离出去。
- 如果你的界面是静态的话,那么你可以定制一个data source类用来专门显示这一块。在多个data source的情况下,每一个data source的子类都可以在自己的section里显示。
- 使用这种方式可以避免很多事情,把数据逻辑拆分的同时还可以把网络访问模块拆出来。
Standard Composition
- 这个可以理解为标准化组合,多个View Controller可以使用View Controller容器管理起来,如果你的view controller由多个逻辑单元组成,那么可以把这种复杂的逻辑拆分到多个view controller中。经验表明这种方式适合一个界面有多个table view或者是多个collection view的情况。
比如在一个界面上包含一个header和一个网格类型的视图,那么我们可以使用懒加载的方式加载这两个view controller,当系统用到的时候再去加载资源。
- (TBHeaderViewController *)headerViewController { if (!_headerViewController) { TBHeaderViewController *headerViewController = [[TBHeaderViewController alloc] init]; [self addChildViewController:headerViewController]; [headerViewController didMoveToParentViewController:self]; [self.view addSubview:headerViewController.view]; self.headerViewController = headerViewController; } return _headerViewController; } - (TBGridViewController *)gridViewController { if (!_gridViewController) { TBGridViewController *gridViewController = [[TBGridViewController alloc] init]; [self addChildViewController:gridViewController]; [gridViewController didMoveToParentViewController:self]; [self.view addSubview:gridViewController.view]; self.gridViewController = gridViewController; } return _gridViewController; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; CGRect workingRect = self.view.bounds; CGRect headerRect = CGRectZero, gridRect = CGRectZero; CGRectDivide(workingRect, &headerRect, &gridRect, 44, CGRectMinYEdge); self.headerViewController.view.frame = tagHeaderRect; self.gridViewController.view.frame = hotSongsGridRect; }在结果子视图里,其包含的每个collection view,都展示统一的数据类型,这样更便于管理和修改。
Smarter Views
如果你是在view controller类初始化你所有的子视图的话,那么你应该考虑使用更适合自己的View。UIViewController默认使用UIView,不过同样你可以自定义View实现重写。使用
-loadView来达到这种效果,在这里你只需要把自定义的View设置给self.view即可。@implementation TBProfileViewController - (void)loadView { self.view = [[TBProfileView alloc] init]; } //... @end @implementation TBProfileView : NSObject - (UILabel *)nameLabel { if (!_nameLabel) { UILabel *nameLabel = [[UILabel alloc] init]; //configure font, color, etc [self addSubview:nameLabel]; self.nameLabel = nameLabel; } return _nameLabel; } - (UIImageView *)avatarImageView { if (!_avatarImageView) { UIImageView * avatarImageView = [UIImageView new]; [self addSubview:avatarImageView]; self.avatarImageView = avatarImageView; } return _avatarImageView } - (void)layoutSubviews { //perform layout } @endPresenter
- Presenter(一系列get方法)是从Model中获取数据并提供给View层,Presenter还负责处理后台任务
- 主导器一般包含着model对象,这里的model是用来展示的,所以属性都是暴露出来的。
|
|
- 需要注意的一点是,model对象本身是不暴露出去的。Presenter作为model的看门人,保证了view controller不用避开主逻辑服务,而可以直接访问model层。这种架构减少了依赖性,由于
TBUser的存在,使得model接触的类比较少,因此如果它改变,则牵涉的逻辑比较少。
Binding pattern
- 在形式上,这种可以看做
-configureView。当数据层发生改变的时候捆绑形式就会更新view。Cocoa本身就适合这个,因为KVO可以检测到model层的变动,而KVC可以从model层读取数据然后赋给view,两者实现完美结合。第三方库Reactive Cocoa也是采用了这种方式,但它有点太庞大。 - 这种方式与主导器结合起来效果非常好,一个创建对象来传递值,而另一个去接受然后显示到view上。
|
|
interaction pattern
- 有时候View Controller过于庞大会带来很多你意想不到的问题。View Controller的角色是接受用户操作然后更新views和相应的model。如今的交互变得越来越复杂化,并且还造成了很大的代码冗余。
- 交互常包括很多控件初始化,可选择性的信息输入,和一些事件,比如网络访问和状态改变。其实这种操作的生命周期是可以集成到交互对象里的。下面的例子就是讲button被按下时候的交互事件,但是把交互对象作为action的target,比如:
[button addTarget:self.followUserInteraction action:@selector(follow)]也是很不错的。
|
|
键盘管理
- 在键盘状态改变后更新视图也是一个需要考虑的点,之前有可能是放在了view controller里,但是这个功能可以很容易被移植到键盘管理对象里。当然有很多键盘管理的例子,然而,如果你觉得他们过于繁杂,可以尝试简单的版本:
|
|
- 你可以调用
-beginObservingKeyboard和-endObservingKeyboard,从开始-viewDidAppear到结束-viewWillDisappear或者其他适合的地方。
导航栏
- 界面之间的转场正常情况下是通过
-pushViewController:animated:。如果转场变得复杂了,那么可以考虑把这种操作代理到导航栏对象中,尤其在适用于iPhone/iPad通用的app,导航需要改变依赖于栈中的最顶端的size class。
|
|
- 这种方式凸显出了一大好处是,把大的对象拆成了很多小的模块。他们可能会被修改,重写或者是替换。相比那些复杂臃肿的view controller,你可以把导航栏设置为自定义的
Navigator。