最近在做短视频相关的模块,于是在看 GPUImage 的源码。其实有一定了解的伙伴一定知道
GPUImage
是通过addTarget
链条的形式添加每一个环节。在对于这样的设计赞叹之余,想到了实际开发场景下可以用到的场景,借此分享。
我们的项目中应该有很多的聚合页,每个聚合页上都有 feed 流,而在很多的项目中 feed 流的场景都是可以进行复用的。而在这样的场景下我们希望复用的 feed 流中的 cell 可以在多个界面上进行复用。但是如果每一个 cell 上又有几个点击事件,如果每一个 Controller
上都有一堆的事件处理代码,又会代码冗余量巨大。
开发中遇到的痛点
随便举个例子,微博的信息流,微博很多业务都是这样的界面进行展示,如果每个 cell 的点击事件代理回 controller 中进行执行,那 controller 有多重可想而知... 而且一旦以后业务调整,某些跳转页面更改,波及的页面之广,也是无法接受的。
这个时候就会想可不可以在一个地方固定的处理这些事件?
一些解决方案
我记得对于这个问题, 源神 曾经提出过 self-manager 的概念可以解决类似的问题。在源神的解决方案中,feed 按钮的功能相对单一这样的方式是一种较好的方案,但是 feed 上的按钮根据业务场景做不一样的跳转,这样的处理又该如何处理呢? 其实对于源神的方案其实传入枚举,对它做对应的处理就可以了。
还是用微博进行举例,现在我们有A,B,C三条业务线都会对会有 feed 展示这个 cell。
- A 业务线要求就是要求底部 tabbar 是转发,评论和点赞的功能
- B 业务线的要求是转发的按钮是跳转到业务线 A, 评论按钮的点击事件跳转到业务线C,点赞按钮的功能保留
- C 业务线的要求是转发的按钮跳转到业务线 B, 评论的按钮保留原功能,点赞的按钮跳转到业务线 B 这样的操作。
对于这样的恶心要求(不要觉得我天马行空,我真的遇到过这类似的业务场景,而且也确实有对应的需要),如果此时还是使用 self-manager,来对状态进行判断。可能伪代码的结构大致如下。
typedef NS_ENUM(NSInteger,BusinessLineType){
BusinessLineTypeA = 0,
BusinessLineTypeB,
BusinessLineTypeC,
}
typedef NS_ENUM(NSInteger,CommentBtnHandleType){
CommentHandleTypeA = 0,
CommentHandleTypeB,
CommentHandleTypeC,
}
......
-(viod)configureCellWithBusiness:(BusinessLineType)businessType{
//根据 businseeType 进行判断 将评论点击事件的枚举传入下一级 然后正确响应点击事件
//根据 businseeType 进行判断 将转发按钮事件根据业务线枚举讲点击枚举传入下一级
...
}
不知道大家对于这样的代码看到后的感受是怎么样的,但是我可以设想到,在没有足够的文档说明的情况下,如果组里来了一个新的小伙伴或者让一个对当前业务场景不足够熟悉的小伙伴进行维护,一定很抓狂,感觉这样的形式在维护上的成本还是比较高的。所以这样的方案还是比较适合处理业务上职责比较单一的小块。
此外对于大厂可能有一条业务线的业务代码需要使用到多个产品中的情况,这样的方案一样也就不再适用了。因为 view 层承接了业务。 而两个产品的设计并不相同,但是业务的逻辑是相同的,这个时候就不是简单的替换 view 层就能完成功能添加那么简单的问题了。
曾经的方案
基于上边的复杂业务可读性差和让业务和 view
完全解耦的思路,这个时候就需要思考,是不是有更好的处理方案。在一开始我们的项目出现这样的需求的时候,我想到的解决方案是创建一个事件处理中心的概念。
就是将 cell
中的每一个试图需要响应事件回调到 cell
层,然后 cell
中有一个 delegate
, 创建一个叫 HandleEventCenter
的对象(controller
创建数组维护)来起到回调中心的作用。
这样对于上边的需求,我们的处理方案就会变得更加灵活,可以写一个通用处理一般场景下的基类,然后根据业务线的不同继承自基类,重写需要特殊处理的基类的问题。
这是看下这样处理的优缺点:
解决了
view
层和业务代码之间代码耦合的问题,同时事件的处理不需要在每个controller
上写多次。同时可以较好的应对点击事件在不同业务线处理不同逻辑的问题,而且代码的可读性问题也得到了解决。问题就在源神提到的我们要将事件一层层向上回调,写了很多坨看上去很不爽的回调代码的问题(无论
delegate
还是block
都不够优雅)
更好的处理方案
看到 GPUImage
的源码之后,我当时想到的方式,就是用这种事件链条的形式,将事件传递下去就行了。其实在日常开发中,我们都习惯了使用 delegate
或者 block
两种方式对事件向上进行传递,但是忘记了系统很常用的的 target - action
模式。
下边来看下我们曾经固有模式和现在解决方案的区别。
曾经的方案:我们捕捉到事件执行 -> 传给上一层(block 或者 代理的模式) -> 再上一层 -> 最后的代理中心 -> 事件响应
现在的解决方案:将事件代理中心 -> 传递给 cell 层 -> 传递给各个事件层 -> 在事件执行处调用处理中心对应的方法
这样每层一大堆恶心代码问题得到了解决,也完全抽离了试图和业务之间的耦合性。除此之外,如果我们的 cell 上添加了新的响应事件,不需要在层层响应,只需要在处理中心添加新的代理,然后再执行处 target
调用对应方法即可。感觉很大程度上减少了代码量,同时降低了维护成本。
一些题外话
其实我们在实际的开发中,发现一些特殊的场景下,事件处理中心的方案存在很大的弊端。比如 我们的界面上有一个编辑功能的按钮,按钮点击后,我们会跳转到一个新的界面对 cell 的数据源进行修改,这个时候再返回,我们需要根据新的数据源刷新当前的 cell 。 事件处理中心的处理方案,就不足以解决这样复杂的问题,否则就需要和 tableView 产生耦合。当然这样的问题,我们取巧的进行了解决,本文就不进行介绍了。
提这样的题外话,不过是想表达每一种方案中可能或多或少的都存在各种各样的问题,但是针对问题,我们总是可以找到更好的解决方案。总是思考着,我们的程序也终究会变得更好。
当然本文中提到的方案也可能存在各种各样的问题,希望不吝赐教,希望在探讨中找到更平衡的方案。共同进步~~~
最后
之前看源码,一直关注点都在于技术的细节和如何解决问题上。但是经此发现,在读源码的过程中,真的可以思考那些优秀的开源的代码,为什么这样设计,在我们日常开发中,这样的设计是不是可以得到推广和应用。我相信在这一过程中,不知不觉,大家都会获得足够的成长和收获~~~