原文链接:http://www.jianshu.com/p/6d5327111511
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
正文
一、关于横向模块开发
团队型项目开发中,往往是根据功能模块进行开发任务分工的,如:商品模块、社交模块、设置模块等等,但是模块与模块之间,往往存在着一些横向的、通用的小功能,如:日历选择、出发地选择以及本文要谈到的搜索控制器。
做横向模块和封装框架是一样一样的,最最重要的,是接口的设计。如何增强模块的通用性、减少侵入性,让该模块的使用者用的爽,在设计接口的时候需要充分考虑的。
-
所谓通用性,是指能在多大程度上让别人用。做的最好的应该是苹果的UITableView。UITableView被称为“SB控件”,因为他不用知道自己展示什么内容,不用知道自己需要展示多少行,一切差异化的东西,全部通过代理来获取,所以他能拥有极其强大的通用性,所有想要做表格型展示的人都能用。
- 然而,通用性也有它的弊端,就是会额外增加使用该控件的开发人员的工作量。
- 假设项目中用到的所有tableView,都有相同的tableHeaderView、相同的numberOfSection & numberOfRow。如果还是用官方原生的UITableView,那我们每次使用,都需要写相同的、重复的代码来设置。
- 这个时候我们就应该通过二次封装的方式,主动降低通用性,来减少重复代码。
- 所以,不管是产品还是技术,没有绝对好的方案,只有在一定范围内,最适用的方案。技术就是权衡。
-
所谓侵入性,是指别人用了你的控件,一旦不想继续用了,要花费的调整代码的精力会不会非常大。推荐杰哥的MJExtension,这个神一样的框架基本上做到了零侵入性。
OK,装B完毕,咱们来看看这个“搜索控制器”的需求。
二、需求
- 不同板块相同需求
1.1 右上角的“搜索”按钮title,没输入关键词,展示“取消”;输入了关键词,展示“搜索”
1.2 没搜索到结果,关键词高亮现实
1.3 搜索有结果,展示搜索结果
1.4 请求数据时,转菊花 - 不同板块不同需求
2.1 板块一
2.2 板块二
2.3 板块三
三、思考
问题:
- UI层面,不同的元素如何如何处理?
- 如:搜索框的placeHolder,是让使用者传字符串进来?还是传type进来,我们根据传进来的type,在控制器内设置不同placeholder?
-
数据层面,请求数据的逻辑在搜索控制器内部写死?还是让外部把搜索结果传进来?
-
其他
- 接口方法设计为类似MBProgressHUD的“+ (void)showXXXXX”型,还是设计成普通的返回一个实例控制器,让使用者决定何时弹出该控制器?
结论
- UI层面的差异比较简单明了,不涉及到后续一些东西,在控制器内部设置比较方便,采用传入type方式
- 数据层面的东西(包括网络请求和本地持久化),逻辑不那么简单直接,而且不同板块应该请求哪些接口、本地存储存在什么地方,显然负责做该板块的同学比我们更清楚,所以最好的办法是把请求数据之类的事情交给外部去做,我们只要提供一个方法,让外部能把请求好的数据转换成字符串数组传进来,我们负责展示就好。
- "+ (void)showXXX"方法用起来显然更爽,而且我们不会把过多的细节暴漏给该控制器的使用者,可以限制使用者的权限,防止使用者进行一些不恰当的操作导致崩溃(顺便插一句,相对于window来说,mac OS 就是这样做的。不用关心什么杀毒软件、硬盘分区之类的破事儿,写代码就安心写代码,做设计就安心做设计,系统方面的东西可以不用关心...这也是macOS用起来更爽的原因之一)
思路整理
现在,咱们想做这样一个搜索控制器:
- 足够封闭,不把搜索控制器对象返还给使用者
-
有足够的通用性,请求数据、数据存储等事情交给调用者处理,我们的搜索控制器只负责数据展示
1.1 这意味着,我们需要告诉调用者(数据逆传):- 什么时候应该请求网络数据(点击了搜索按钮)
- 什么时候应该清空本地缓存(点击了清除按钮)
- 用户点击了哪条搜索结果
1.2 调用者需要告诉我们(数据顺传):
- 请求回来的数据是什么
-
请注意,通常我们进行数据顺传的方式,是拿到某个控制器,点出来他的属性,并进行赋值;而咱们想要的效果,是最大程度限制调用者的权限,不让调用者拿到咱们的搜索控制器(意味着不能点出来)的同时,最好限制调用者只能通过咱们给定的方法告诉咱们搜索结果(不让调用者拿到搜索控制器的属性)。
怎么办?有什么办法能不返还给调用者一个对象,只返还给调用者一个方法?答案是block。
数据逆传方面,个人也还是非常喜欢block。因为相比于代理,block能够让代码实现“高聚合、低耦合”,不用跳来跳去的找代码,维护起来更方便。
(下一篇:一个复杂的首页的独白中,咱们会用到将代理和数据源方法,全都变成block的tableView)
四、上代码吧
这篇文章主要讲的,其实还是接口设计。所以代码就只贴头文件吧,源码丢在Github上了嗯(源码传送门)...如果觉得还有那么点小启发的话,记得顺手点星哦^_^
1 文/Azen(简书作者) 2 原文链接:http://www.jianshu.com/p/6d5327111511 3 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。 4 5 // 6 // AZXSearchController.h 7 // AZXSearchControllerDemo 8 // 9 // Created by Azen.Xu on 15/12/5. 10 // Copyright © 2015年 Azen.Xu. All rights reserved. 11 // 12 13 #import <UIKit/UIKit.h> 14 15 typedef NS_ENUM(NSInteger, AZXSearchControllerType) // 搜索控制器类型 16 { 17 AZXSearchControllerTypePartOne = 1 << 0, // 板块一 18 AZXSearchControllerTypePartTwo = 1 << 1, // 板块二 19 AZXSearchControllerTypePartThree = 1 << 2 // 板块三 20 }; 21 22 typedef NS_ENUM(NSInteger, AZXSearchFunctionType) // 点击事件类型 23 { 24 AZXSearchFunctionTypeClear = 1 << 0, // 点击了"清除搜索历史"按钮 25 AZXSearchFunctionTypeSearch = 1 << 1, // 点击了"搜索"按钮 26 AZXSearchFunctionTypeSearchArray = 1 << 2, // 点选了搜索结果列表 27 AZXSearchFunctionTypeHotArray = 1 << 3, // 点选了热门搜索列表 28 AZXSearchFunctionTypeHistoryArray = 1 << 4, // 点选了历史搜索列表 29 AZXSearchFunctionTypeCreatTagForDiscover = 1 << 5 // 点选了创建标签 30 }; 31 32 typedef void(^AZXSearchCallBack)(AZXSearchFunctionType selectedType , NSInteger selectedRowIndex , NSString *resultString); // 点击回调 参数一:点击事件类型 参数二:选中行号 参数三:选中文字 33 typedef void(^AZXSearchSetNewArrayHandle)(NSArray *newArray); // 通过此block传递搜索结果字符串数组 34 35 36 @interface AZXSearchController : UIViewController 37 38 /** 39 * 根据type创建不同展示样式的搜索控制器,返回搜索结果handleArray 40 * 41 * @param fromController 来源控制器 42 * @param hotArray 热门搜索stringArray 43 * @param hisArray 历史搜索stringArray 44 * @param type 样式枚举 45 * @param calBack 回调 - 数据请求成功后请为handleStringArray重新赋值 46 * 47 * @return 搜索结果handelArray 48 */ 49 + (AZXSearchSetNewArrayHandle)showSearchControllerFromController :(UIViewController *)fromController 50 withHotModelArray :(NSArray *)hotArray 51 hisModelArray :(NSArray *)hisArray 52 type :(AZXSearchControllerType)type 53 callBack :(AZXSearchCallBack)callBack; 54 55 @end