###当我们谈论iOS架构的时候我们在谈论什么
- 模块化
- 规范化
- 统一行为
本文主要就以上模块化,以网络请求模块的抽取为例稍作讨论。
###模块化
模块化的目的是实现解耦,提升模块、组件的复用性。一个很简单的例子,App需要与服务器交换数据,最差的做法,是把网络请求、回调、业务处理、界面操作全部写在ViewController
里。如果把网络请求抽取出来,建一个Webservice
类,仅负责最基本的RESTful请求,并在回调中处理好类似404,无网络等各种网络状况。那么,这个类就变成了通用的网络服务处理模块,便于维护和复用。
###遇到的问题
在划分模块的一个前提是,模块不要对其他层有依赖性。最好的情况是,这个模块拿到任意一个项目都可以直接使用。在这一点上,我遇到了一个值得思考的问题。
在iOS客户端里,网络请求可以分为两种,一种是静默请求,也就是在用户不知情的情况下默默地进行网络请求处理。典型的例子有微信朋友圈点赞。用户在点赞时,UI直接进行点赞成功的反馈,同时网络请求在静默进行,不阻碍用户的正常操作。另一种是显示请求,用户需等待操作完成后才能进行下一步操作。典型的例子一种是下拉刷新,另一种是支付宝付款。付款的请求中,UI暂时显示一个Loading图标,用以提示用户程序正在处理,而非卡死(设计思想:反馈 ——《iOS Human Interface Guidelines》)。
遇到的选择,是在第二种显示请求的情况下如何更好地封装。我所负责的这个项目,除网络请求外,并没有耗时很长的I/O等情况需要处理。因此,有两个方案。
将UI的Loading封装在WebService中,仅暴露isShowIndicator
和toView
两个参数在网络请求的接口中
- (void)requestWithPath:(NSString *)path
parameters:(id)parameters
isShowIndicator:(BOOL) isShowIndicator
toView:(UIView *)toView
labelText:(NSString *)labelText
success:(SuccessBlock)success
failure:(FailureBlock)failure;
或者,将UI的Loading封装在BaseViewController中,作为一个ViewController的基础行为,相应的业务模块需要显示请求时,调用显示指示器方法。
@interface BaseViewController : UIViewController
- (void)showTips:(NSString *)tips;
- (void)showIndicatorWithTitle:(NSString *)title;
- (void)hideIndicator;
@end
前一种方式在写具体的业务逻辑时非常方便。只需要在调用前考虑一下这个请求是否为静默请求,然后将BOOL
和View
参数传入此方法中即可。这样封装的==好处==是,可以在网络层的回调中,处理好指示器的隐藏,以免出现忘记调用Hide方法的情况,并且ViewController
不必因为需要这两个方法,就继承BaseViewController
。==缺点==是不够灵活,如果有不同的业务需要处理不同的显示,隐藏方式,还是需要在ViewController
中重新写一下。
后一种方式在写具体的业务时==缺点==是略微麻烦一些,需要考虑好相对应业务具体情况,具体调用显示隐藏指示器,优点是灵活,并且做到了网络层的去UI依赖。
就MVC模式来说,UI
响应数据的处理,应当由Controller
负责调度。苹果自己在设计的时候,也都遵循了这一设计模式。WebService
层,可以算作Model
层的一个工具。而Model
层与View
层,在iOS的设计上是绝对不允许直接交互的。引用一张斯坦福大学iOS7公开课上的图来形象地展示一下。
Model和View之间是双黄线,就像开车一样,绝对不能压过双黄线 :)至于为什么,我们以后讨论到MVC设计模式再详细讨论。
因此,在WebService中封装了View,实际上是在Model层操作View。多了这么一个View,多出来的事可就多了。我们不仅需要Import进UIKit,还需要Import通用的Indicator。这一依赖就产生了模块之间的依赖,原本为了解耦合而提取的WebService层反而耦合了Indicator。同时,也造成Webservice的粒度增大。需要处理的事情变得更多。
反观第二种方法,需要所有ViewController继承BaseViewController。继承本身是紧耦合的。然而纵向的紧耦合有时候减少了重复工作,将一些相同的行为,做统一化的处理,并且没有造成横向跨层的依赖。类似于无网络时的处理,网络请求出错时的处理,Token过期的处理,都应该放在BaseViewController里面。因为这是所有Controller都需要面对的问题。因此,继承BaseViewController是必要的。在统一行为之余,把这几个方法封装在BaseViewController中,又让WebService层实现了细颗粒的模块化组件化。同时,Controller本身就需要控制View,不会因此Import进许多不相关的模块。所需要付出的仅仅是在网络请求结束的时候别忘记调用Hide方法,不然测试小妹的“界面失去响应”BUG就会指派到我们头上 :(
最终,我们的Webservice是封装成这样的:
/**
* post a request to server
*
* @param path the request path append at BASEURL
* @param parameters parameters
* @param token Authorization token
* @param success when success goto this block
* @param failure when failure goto this block
*/
- (void)postWithPath:(NSString *)path
parameters:(NSDictionary *)parameters
token:(NSString *)token
success:(void (^)(id JSON))success
failure:(void (^)(NSError *error, id JSON))failure;
综上所述,模块化所需要遵循的思想就是:轻,少,专。其实思想和我们写方法的时候考虑的“一个方法只做一件事”有类似的地方。
That’s all,hope you enjoy it :)