如何在iOS中优雅的处理网络请求

在iOS开发过程中,如何为UIViewController瘦身是一个常见并且复杂的问题。为什么常见?如果没有精心的设计,UIViewController很容易变成很厚重、冗余。为什么复杂?在真实的项目中,都有各自的历史问题,要在现有的代码中瘦身是个非常有挑战的工作。

其实所谓的瘦身,核心就是要单一职责,这个跟面向对象的设计一样。像MVVM也是将之前UIViewController中处理的工作转换到其它的对象中。

在移动端开发的过程中,总是避免不了请求各种网络接口,这是移动App的特点。那么与网络接口交互的代码是一个界面的重要组成部分,这些部分完全可以独立成单独的层。而这些网络接口的请求和处理也都有自己的业务逻辑。

那么应该如何把网络接口交互的代码独立出来呢?我的答案是把单个接口封装成单独的类,这个类接收请求的参数,处理接口返回的结果。在PP的时候就已经在实践这种模式了,效果还不错。在最近的项目中,通过整合AFNetworkingMantle这2大常用库,完成了最新的网络框架GQDataController,她可以自动的将接口返回的JSON转成Mantle对象。项目地址见:https://github.com/gonefish/GQDataController

下面我们将通过一个例子来描述如何使用GQDataController将网络请求处理的代码封装成单独的类。

要开始使用GQDataController,必须先创建自己的子类,就像你继承UIViewController一样。

.h文件

@interface BasicDataController : GQDataController

@property (nonatomic, strong) NSString *ip;

@end

你可以添加自定义的属性,用于保存接口返回的结果。

.m文件

@implementation BasicDataController

- (NSArray *)requestURLStrings
{
    return @[@"http://httpbin.org/ip"];
}

- (void)handleWithObject:(id)object
{
    [super handleWithObject:object];
    self.ip = [object objectForKey:@"origin"];
}

@end

requestURLStrings用于返回请求接口的地址,支持多个地址备用。子类重写handleWithObject:方法,并将返回的JSON对象赋值给实例属性。

在ViewController中创建实例,然后调用request开始请求(默认是GET请求,子类可以重写requestMethod来进行修改)最后通过Delegate方法处理结果,非常简单清晰的流程。


- (void)viewDidLoad {
    [super viewDidLoad];

    self.basicDataController = [[BasicDataController alloc] initWithDelegate:self];

    [self.basicDataController request];
}

- (void)dataControllerDidFinishLoading:(GQDataController *)controller
{
    self.label.text = [NSString stringWithFormat:@"IP: %@", self.basicDataController.ip];   
}

对UIViewController来说,它并不需要知道请求的是什么接口和如何处理结果。这种模式与MVVM类似,但服务的对象稍微有点区别。所以,你可以在老代码中使用这种模式,也可以与ViewModel一起使用。

上面的例子只是GQDataController的基本使用,在对接口的结果处理上是采用手动保存的。GQDataController另外一个非常重要的功能是提供了对Mantle的支持,你可以自动的将返回的JSON结果转换成指定的Mantle对象。

你只需要在.m文件中添加如何代码

- (Class)mantleModelClass
{
    return [IP class];
}

mantleModelClass返回用于转换成Mantle的类型,IP是一个Mantle子类。另外,子类也不需要重写handleWithObject了。

在ViewController中修改Delegate方法的内容

- (void)dataControllerDidFinishLoading:(GQDataController *)controller
{
    self.label.text = [NSString stringWithFormat:@"IP: %@", self.basicDataController.mantleObject.origin];   
}

GQDataController会将转换的结果保存在mantleObject和mantleObjectList中。如果转换的JSON是字典对象会保存在mantleObject中,如果是数组对象则会保存在mantleObjectList中。

@property (nonatomic, strong, nullable) __kindof MTLModel<MTLJSONSerializing> *mantleObject;

@property (nonatomic, strong, nullable) NSMutableArray<__kindof MTLModel *> *mantleObjectList;

GQDataController还提供了分页、DataSource、接口重试、接口Stub的支持,更多使用可以参考项目的Demo

通过使用GQDataController,强迫你把所有的接口都从UIViewController中分离出来,一方面减少了UIViewController的负担,另一方面也提高了接口对象的复用程度。而且,集成GQDataController非常简单,对现有代码模式影响非常小,你可以轻松的转换过来。

最近的这篇文章《 猿题库 iOS 客户端架构设计》也提到了类似的解决方案。