iOS控制器瘦身-面向超类编程
今天写这篇文章的目的,是提供一种思路,来帮助大家解决控制器非常臃肿的问题,对控制器瘦身。

滴滴 老司机要开车了
如果手边有项目,不妨打开工程看一下你的控制器代码有多少行,是不是非常多?再看一下tableView的代理方法cellForRow和heightForRow的代码是不是也是非常多?里面夹杂着switch和大量if esle的判断逻辑的代码。后期维护看着这些if else是不是特别烦躁?特别是自己在维护前人写的代码,并且还没有注释 一团糟,是不是有更想骂人的冲动?别怕,这里给您提供一种解决思路,让你的tableView代理方法再也没有这种让人头疼的if else判断逻辑,让你的控制器代码量大大减少,并且后期维护成本也大大的减少。
在说具体解决思路前,先给大家简单复习一下MVC和MVVM,因为今天的主题也是和MVVM有关系。MVC模式大家都很熟悉了,就是Model,View,Controller三层,Model负责数据层,Controller负责业务逻辑层,View负责界面显示层,Model和View通过Controller来实现桥接交互,程序的扩展性很好,好处多多。但是呢,MVC也有它自身的缺陷,那就是控制器太臃肿,如果你想在控制器中定位某一个点是比较麻烦的事。

那么为什么控制器如此庞大,就是因为tableView的代理方法里的cell 判断逻辑全在控制器,以及网络请求也在控制器发起,另外还有一些其他的业务逻辑。有没有什么更高的模式呢?MVVM模式就是MVC模式的升级版。
MVVM中Model依然负责数据层,Controller单单负责View的展示和更新,其他业务逻辑不管。View依然负责界面显示。那么ViewController之前负责的业务逻辑现在谁来负责呢?我们再新建一个ViewModel层,处在ViewController层和Model层之间,专门负责业务逻辑,以及网络请求等任务。ViewController从ViewModel中获取数据然后显示在View上,它并不和Model层直接打交道,和Model层直接打交道的是ViewModel层 。
下面附上一张经典gif图片,帮助大家理解两者之间的关系。

MVC与MVVM的关系
好了,前面的都是回顾一些相关知识,为理解接下来的内容做基础,如果想要深入了解MVC,MVVM可以网上找下,这类文章很多。
大家可能疑惑到底什么是面向超类编程,其实就是围绕继承这个特性,子类cell继承父类cell,面向父类这个对象来编程,最终对控制器的tableView进行瘦身,也不止是对tableView优化,配合MVVM新建ViewModel可以抽离很大一部分控制器的代码。现在还不清楚没关系,下面会有很详细的描述让你明白。^_^ 我下面先把大家常用的控制器tableView代理方法的写法,给黏贴出来,然后再用新的面向超类的写法给黏贴出来,大家就可以明显体会到使用面向超类写法的好处了。

老方式大众写法

面向超类编程写法
看到这里,可能会有人吐槽了,新写法就比老式写法少了十几行嘛?其实不是这样的,首先这个demo我只写了三个cell作为例子,真实的项目极少一个控制器只有三个cell吧?控制器的cell越多,好处越明显, 因为在后期不管添加多少cell,控制器tableView的代理方法中的代码几乎都不会增加,相当于构建了一个模版,只需要在新添加cell的内部配置即可。其次,真实的项目也不可能业务逻辑这么简单吧?肯定在if else中嵌套了很多其他的逻辑代码,致使tableView看起来很臃肿。
面向超类编程的好处:
1.控制器瘦身。[控制器内部代码量大幅度减少,逻辑更加清晰]
2.后期维护成本大大降低。[后期如果想添加或者删除cell,只需要新建或者删除一个子类cell,在viewModel中添加或删除一个identifier即可,控制器几乎不用加任何代码]
面向超类的坏处:
1.新建更多的cell文件和一个viewModel文件,包大小会响应增加。
下面就具体讲解面向超类编程瘦身大概要做什么:
一、新建一个继承自UITableViewCell的父类cell
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#import (UIKit/UIKit.h)(因识别问题此处用圆括号替换尖括号)#import "ResponseNewProgrammeData.h"#import "NewProgrammeCellHeightProtocol.h"//子类需要有回调事件的代理@protocol NewProgrammeTableViewCellProtocol <nsobject>- (void)cell1DidSelectedRightButton;- (void)cell2DidSelectedRightButton;- (void)cell3DidSelectedRightButton;@end@interface NewProgrammeBaseCell : UITableViewCell <newprogrammecellheightprotocol>@property (nonatomic, weak) id<newprogrammetableviewcellprotocol> delegate;@property (nonatomic, strong) ResponseNewProgrammeData * responseNewProgrammeData;@end</newprogrammetableviewcellprotocol></newprogrammecellheightprotocol></nsobject> |
首先,要包含控制器的数据源,因为子类cell的UI等操作全靠这个父类的数据源。
其次,要实现NewProgrammeCellHeightProtocol协议,作为计算高度用,具体用法在第二点讲解。
最后,如果子类cell有点击事件需要回调操作的,可再写一个协议NewProgrammeTableViewCellProtocol作为属性持有,在控制器中将delegate指向控制器作为回调使用。
二、新建一个NewProgrammeCellHeightProtocol
|
1
2
3
4
5
6
7
8
|
#import (Foundation/Foundation.h)(因识别问题此处用圆括号替换尖括号)//针对cell的高度写的协议@protocol NewProgrammeCellHeightProtocol <nsobject>@optional+ (BOOL)isStaticCell;+ (float)cellHeight;@end</nsobject> |
+ (BOOL)isStaticCell方法是在子类中使用的,如果当前cell是高度固定的静态cell,就在返回YES,并且在cellHeight方法中返回固定高度。否则返回NO即可,也不需要写+ (float)cellHeight方法。这两个方法会在控制器的heightForRow方法中使用,计算当前cell高度。
三、新建控制器所需要的所有cell,且继承自刚才的父类cell
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#import "NewProgrammeCell1.h"@interface NewProgrammeCell1 ()@property (nonatomic, weak) IBOutlet UILabel *lblName;@end@implementation NewProgrammeCell1@synthesize responseNewProgrammeData = _responseNewProgrammeData;- (void)setResponseNewProgrammeData:(ResponseNewProgrammeData *)responseNewProgrammeData{ _responseNewProgrammeData = responseNewProgrammeData; self.lblName.text = _responseNewProgrammeData.string1;}+ (BOOL)isStaticCell{ return YES;}+ (float)cellHeight{ return 44;}- (IBAction)didPressedPush:(id)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(cell1DidSelectedRightButton)]) { [self.delegate cell1DidSelectedRightButton]; }}@end |
这个cell的高度是固定静态的,所以isStaticCell方法返回YES,cellHeight返回高度。
这个cell中可以拿到控制器数据源,根据自己的需要去获取数据。
这个cell的didPressedPush方法是模拟需要点击事件的,回调给控制器。
其他cell的配置都大致是这样的。
四、新建viewModel文件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#import "NewProgrammeViewModel.h"static NSString * const NewProgrammeCell1Identifier = @"NewProgrammeCell1";static NSString * const NewProgrammeCell2Identifier = @"NewProgrammeCell2";static NSString * const NewProgrammeCell3Identifier = @"NewProgrammeCell3";@implementation NewProgrammeViewModel- (NSArray *)getIdentifierList{ return @[NewProgrammeCell1Identifier, NewProgrammeCell2Identifier, NewProgrammeCell3Identifier];}- (void)requestData{ self.responseNewProgrammeData = [[ResponseNewProgrammeData alloc] init];}@end |
viewModel负责配置控制器所需要注册的cell以及真正要显示的cell,getIdentifierList返回需要注册的所有cell。因为某些页面的cell不是固定显示的,可能根据数据源动态的来配置。同时,viewModel也负责网络请求数据解析等其他业务逻辑代码。这里的viewModel相当于MVVM模式中的胖Model,不仅处理网络请求,还处理页面UI的配置等其他业务逻辑,这样就不会使控制器那么臃肿。
五、在控制器中做相应代码配置
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
- (void)configTableViewCell{ for (NSString * identifer in [self.viewModel getIdentifierList]) { [self.tableView registerNib:[UINib nibWithNibName:identifer bundle:nil] forCellReuseIdentifier:identifer]; }}#pragma mark - UITableViewDelegate- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return self.viewModel.getIdentifierList.count;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ NSString * cellIdentifier = [self.viewModel.getIdentifierList objectAtIndex:indexPath.row]; NewProgrammeBaseCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; cell.delegate = self; cell.responseNewProgrammeData = self.viewModel.responseNewProgrammeData; return cell;}- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ NSString * cellIdentifier = [self.viewModel.getIdentifierList objectAtIndex:indexPath.row]; Class<newprogrammecellheightprotocol> cellClass = NSClassFromString(cellIdentifier); CGFloat height = 0; if ([cellClass isStaticCell]) { height = [cellClass cellHeight]; return height; } else { NewProgrammeBaseCell * cell = (NewProgrammeBaseCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath]; height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingExpandedSize].height; return height; }}</newprogrammecellheightprotocol> |
首先,要实例化viewModel对象,在configTableViewCell方法中获取需要注册的所有cell,遍历注册。
其次,在cellForRow代理方法中,从viewModel对象中获取所有注册的cell的identifier,然后从tableView中获取赋值给父类cell。再针对父类cell做一些赋值操作,也就是分别调用了子类cell,充分利用多态的特性。
最后,在heightForRow代理方法中,仍然是根据viewModel对象获取所有注册的cell的identifier,再根据identifier反射成对象子类cell的类对象,接着调用cell中的协议方法,来计算高度。
到这里,面向超类编程瘦身基本思路都说完了,这里有完整的demo[点击下载源码](https://github.com/RadishLin/XLFaceParentClassProgrammeDemo),如果喜欢动下小手点个赞,谢谢啦????~自己可以在项目中尝试下,可能在实际应用中会有其他没有想到的问题,比如说,当前控制器的cell最多显示3个,但是在某种情况下是显示2个,有1个不需要显示。那么我们在viewModel中应该怎么配置getIdentifierList数据源呢?我们可以这样做,获取最新的数组。不同的项目业务逻辑不一样,写法也会有差别,具体问题具体分析。
|
1
2
3
4
5
6
7
8
|
- (NSArray *)getNewIdentifierList{ NSMutableArray * newList = [[self getIdentifierList] mutableCopy]; if (**判断条件**) { [newList removeObject:NewProgrammeCell2Identifier]; } return [newList copy];} |
希望本文的瘦身思路可以帮助您,如果此文哪里有纰漏,或者您有什么更好的建议,欢迎提出来,大家一块探讨。
转载:http://www.jianshu.com/p/2b6e79f86d74
iOS控制器瘦身-面向超类编程的更多相关文章
- iOS代码瘦身实践
1 分析当前ipa的组成 一般一个ipa会包含: 1) 资源文件 本地文件:数据.配置.数据库等等 字体文件 图片资源 2) 源代码 通过生成linkmap文件,分析源代码生成的编译文件的大小.在B ...
- iOS可执行文件瘦身方法
缩减iOS安装包大小是很多中大型APP都要做的事,一般首先会对资源文件下手,压缩图片/音频,去除不必要的资源.这些资源优化做完后,我们还可以尝试对可执行文件进行瘦身,项目越大,可执行文件占用的体积越大 ...
- 【转】iOS可执行文件瘦身方法
http://blog.cnbang.net/tech/2544/ 缩减iOS安装包大小是很多中大型APP都要做的事,一般首先会对资源文件下手,压缩图片/音频,去除不必要的资源.这些资源优化做完后,我 ...
- iOS App 瘦身方案
缩减iOS安装包大小是很多中大型APP都要做的事,一般首先会对资源文件下手,压缩图片/音频,去除不必要的资源.这些资源优化做完后,我们还可以尝试对可执行文件进行瘦身,项目越大,可执行文件占用的体积越大 ...
- iOS图片瘦身总结
前言 最近在公司写了个小程序来为iOS应用中的图片瘦身,进而减小APP大小,减少用户下载时的流量. 瘦身是在一个专门为图片瘦身的网站进行的. 地址:https://tinypng.com 这个网站提供 ...
- iOS 可执行文件瘦身方法
编译选项 1.编译器优化级别 Build Settings->Optimization Level有几个编译优化选项,release版应该选择Fastest, Smalllest,这个选项会开启 ...
- iOS ViewControllers 瘦身
https://objccn.io/issue-1-1/ https://juejin.im/user/57ddfba4128fe10064cbb93a 把 Data Source 和其他 Proto ...
- IOS APP 瘦身
只保留其中一宗编译环境包 lipo -thin armv7 XXAPP -output XXAPP.armv7
- iOS架构师之路:控制器(View Controller)瘦身设计
前言 古老的MVC架构是容易被iOS开发者理解和接受的设计模式,但是由于iOS开发的项目功能越来越负责庞大,项目代码也随之不断壮大,MVC的模糊定义导致我们的业务开发工程师很容易把大量的代码写到视图控 ...
随机推荐
- Java EE 锚、表格用法
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- C#登录窗口及验证(+SQL)
团队成员及分工 团队: Blue 团队共有六人 姓名: 学号后四位: 贡献分: 张 宇(队长) 1152 1+1.7=2.7分 丁志愿 1 ...
- mysql数据小姿势
CREATE TABLE `information` ( `NUMBER` bigint(20) NOT NULL AUTO_INCREMENT,//将number设为自增字段 `USER_NAM ...
- [SAP ABAP开发技术总结]物料、生产、采购、销售长文本
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- JAVA操作数组
使用 Arrays 类操作 Java 中的数组 Arrays 类是 Java 中提供的一个工具类,在 java.util 包中.该类中包含了一些方法用来直接操作数组,比如可直接实现数组的排序.搜索等 ...
- Codeforces Round #249 (Div. 2) A题
链接:http://codeforces.com/contest/435/problem/A A. Queue on Bus Stop time limit per test 1 second m ...
- 20150203一些移动端H5小bug解决
都是一些我也不知道为什么会有的bug. 1. 在三星note2,小米2,页面加载后,页面有黑块. 那么提高被盖住的部分z-index. 2. iphone5 ,ios7.0.4,上文字显示不出 那么就 ...
- WdatePicker.js 日历点击时,触发自定义方法 ,可以调用自己的函数。
问题: 在选择日期后,没有提交按钮,得到日期后,就可以把日期传到后台,然后就可以得到数据. 方法: 在input 标签中加入onfocus ,就可以了. wdatePicker();可以自定义事件函数 ...
- MVC3远程验证
public class StudentModel { [Display(Name="学生编号")] public int StuId { set; get; } [Require ...
- iOS - TouchLock 手势解锁
1.手势解锁的创建 代码封装见 QExtension QLockView.h #import <UIKit/UIKit.h> @interface QLockView : UIView / ...