iOS面向协议编程应用

1、UIKit中的协议编程

面向协议编程即面向接口编程,在iOS中大家比较熟悉的协议,比如UITableViewDelegateUITableViewDataSource,apple为什么要设置这样两个协议呢?

  • 在使用过程中,我们知道,通过delegate方法,我们可以处理tableView的一些响应事件,比如点击了cell,通过dataSource,我们设置好tableView的数据源,几行几列,采用的什么样的cell,UITableView框架在获取了我们提供好的数据源之后,就可以展示出一个滑动列表出来,并提供了各种API接口来给我们做各种各样的事情,试想一下,如果我们开发过程中,苹果没有提供这样一个UITableView框架,我们是不是得造一个这样简单易用的轮子呢
  • 事实上,在现在的各种编码社区中,都存在很多优秀的轮子,造轮子的目的是为了让别人能尽可能少的做一些复杂繁琐的操作,而快速达到某个目的,我们在平时开发的过程中是不是很感谢这些第三方大佬提供的各种优秀库,优秀API
  • 回过头来再看看UITableView的架构,实际上,这个封装好的UIVIew框架,把所有复杂的整体性的事情都做了,开发者只需要对某一个cell的数据源和事件做相应的处理,特点如下:
    • UITableView只负责整体框架搭建
    • 框架与数据源无干扰
    • 框架与事件无干扰

2、协议编程项目运用

在项目开发中,特别是团队协作,接手别人的项目,总感觉代码乱入麻,比如在某一个模块增加一个功能或者维护一个功能,代码逻辑跳来跳去,需要改动众多代码,动老旧业务和新需求都存在很大风险,那么我们不妨仿造下UITableView的这种面向协议编程的方式,来优化下我们在业务开发中的架构

  • 举一个iOS开发常遇到的例子,UIViewController里包含一个UITableView,假如涉及5中不同类型的cell,即有五种不能类型的业务要需要处理,每种cell或者说每种业务可能有多个子功能,比如说点击cell的按钮a,点击按钮b,等,业务多起来后,整个controller容器就显的不能看了,具体表现为一下几种现象:
    • cellForRow:方法,根据不同的业务类型,创建获取cell,赋值cell,众多的if else /switch,写的在一个视图获取函数里
    • didSelected:,根据不同的业务类型,众多的if else /switch处理不同的业务

我们发现,只要与业务挂上钩,由于业务是在不断地更新迭代,就很容易出现以上问题,但是项目不可能没有业务的存在,那么如何合理的拆分业务,设计合理的架构呢

  • 比如我们常说的组件化、模块化,这是一种大的,某个比较完整的功能模块的拆分
  • 比如说mvvm,mvp等设计模式,也能达到业务拆分的效果

如何把cellForRowdidSelected也从业务中脱离出来呢?

其实仔细想想,所有的数据源在确定之后(本地的或者服务端返回的),就已经确定某一条数据源,对应什么cell,需要处理那些事件,即数据源和事件都已经确定好了,我们可以在参考mvvm里的vm模型,我们也可以设计这样的一个model,既提供cell需要的展示数据,也可以处理cell的事件.

有了这一步的vm想法,但是并不想采用mvvm的结构,vm有点太固化了,比如说相同的类型的cell,如果存在巨大的业务差距,用同一个vm模型去处理就不是那么合理了,那么应该怎样去优化这种结构呢?上面提到的协议就是一个不错的选择。制定好协议方法,让model去遵守协议方法就好了

一个协议,可以被相同cell类型,但是业务类型不一样的model遵守,这样就可以实现业务隔离,但是采用mvvm的形式去实现cell样式一样,业务又不一样的效果,那就要用继承方式,先创建一个basevm,然后根据业务去创建不同的subvm。继承和协议各有优劣势,但是如果在这种业务实现上采用继承,会比协议更加重,继承需要的文件会多一个baseVM类,过多的依赖继承,会使相关继承业务的逻辑处理流程变得杂糅。

3、面向协议编程Demo

最终实现效果如下


3.1几个关键因素
  • UITableView
  • 两种cell
  • 5个业务,即5个viewModel,如果存在可以复用的vm,可以减少vm个数
3.2demo设计架构图
3.3关键代码

下面通过各个模块的代码来感受下这样设计的好处【由于条件不便加偷懒,代码以图片形式展示】
GLProtocolProgramDemoVC

  • 获取数据源


1、5个model对应5个业务
2、style1model1、style1model3,因为业务逻辑相同,公用同一个viewmodel,即GLPPDStyle1Model1
3、style1model2虽然样式和style1model1、style1model3相同,但是由于业务处理形式不一样,需要自己处理,所以单独建一个model,具体可以查看GLPPDStyle1Model1GLPPDStyle1Model2中的,didSelected方法处理区别
4、style2model1、style1model2由于需要与vc交互(上面的跳转是模拟的一个交互),这个交互由于与具体的业务无关,即与vm无关,所以需要抛出,由vc处理
5、所有的model遵循基本的协议GLPPDModelProtocol,下面会看到该协议的好处

  • UITableView相关代理方法


1、由于所有的model都遵循GLPPDModelProtocol,我们无需区分是哪种业务类型的model,既可以获取identifier,从而获取对应的cell
2、cell我们也无需关心是什么样式的cell,由于cell都遵循对应的cellProtocol,即实现了configCellWithModel:方法,只是参数model遵循的协议不一样,这里也可以只定义一种cellProtocol,参数遵循GLPPDModelProtocol即可,分开设计,一是为了增加可读性,二是可以针对不同类型的cell,扩展更多API,这里demo仅有configCellWithModel:
3、didSelectedRowAtIndexPath:方法,将事件转发给viewModel,业务的事情就应该由具体的业务处理

Protocol

参考设计架构图protocol设计

Cells

  • GLPPDStyle1Cell.h
#import "GLDemoProtol.h"
@interface GLPDDStyle1Cell : UITableViewCell<GLPPDCell1Protocol>
@end
  • GLPPDStyle1Cell.m
@interface GLPPDStyle1Cell()
@property (nonatomic, strong) UIImageView *iconView;
@property (nonatomic, strong) UILabel *accessLabel;
@property (nonatomic, strong) id<GLPPDStyle1Protocol> model;
@end

- (instancetype)initwithStyle:...
- (void)setupView{
    //cell视图搭建
}
- (void)configCellWithModel:(id<GLPPDStyle1Protocol>)model {
    self.model = model;
    if ([model respondsToSelector:@selector(imageName)]) {
        self.iconView.image = [UIImage imageNamed:[model imageName]];
    } else {
        self.iconView.image = nil;
    }
    
    //同理通过代理方法accessTitle获取accessLabel.text
}

*GLPPDStyle2Cell.m

//基本方法和GLPPDStyle1Cell相同
- (void)clickHandle:(UIButton *)sender {
    if([self.model respondsToSelector:@selector(didClicked)]){
        [self.model didClicked];
    }
}

Models

  • GLPPDStyle1Model1<GLPPDStyle1Protocol>
- (NSString *)identifier {
    return kStyleCellID;
}
- (NSString *)imageName {
    return self.params[@"icon"] ?: @"tubiaozhizuomoban-- 1";
}

- (NSString *)accessTitle {
    return self.params[@"title"]?:@"手机";
}

- (void)didSelected {
    DemoBaseVC *target = self.params[@"target"];
    if(target){
        DemoBaseVC *vc = [[DemoBaseVC alloc] init];
        vc.title = [self accessTitle];
        [target.navigationController pushController:vc animated:YES];
    }
}
  • GLPPDStyle1Model2<GLPPDStyle1Protocol>
    同上述model一样的cell样式,但是可能由于业务不同,需要单独建一个vm,看代码

GLPPDStyle1Model1不同的是,configParams:在处理上有所不同,GLPPDStyle1Model1这个子业务可以从外面传递icontitle信息

  • GLPPDStyle2Model1<GLPPDStyle2Protocol>

1、通过didClicked接受cell的按钮点击事件
2、由于存在外界交互,通过callBack进行回调,与vc交互

3.4代码分析
  • view 、viewModel、vc分离,分工明确
  • vc只负责从本地或者服务器获取数据源,生产viewModel,即createDatas
  • UITableView的相关代理方法处理简单,无需关心数据源类型及业务处理
  • cell只与视图展示、事件响应有关,不做业务具体操作
  • model视图展示数据处理,业务处理
  • 各业务model分工明确,可读性,可维护性强
  • 新增cell样式,或者新增业务类型,对老业务无影响,只需要新增cellProtocolModelProtocol
  • cell样式相同,可复用已有的cell,业务类型相同,可以复用已有的Model,业务不同或者业务比较复杂,也可以新建Model

4.总结

本文章demo采用的是一个UITableView主题结构,实际开发中面向协议编程,主题结构可以是任意的形式的模块,只要存在view组合【UITableView就是由cell组合而成】、业务组合【各种Models】,都可以采用这种形式进行开发,并且可以递归拆解,大拆中,中拆小。
设计模式有很多,这个demo只是面向协议编程的一个小例,项目比较简单,在实际开发中,经常会遇到一个视图包含N多个功能模块,这些功能或相似或不相似,会根据实际情况、个人理解产生一些形变,但是无论用怎么的设计模式,我们需要尽可能遵守软件设计模式的六大准则,减少后续开发成本,迭代风险

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342