iOS

iOS的网络框架(HTTP)

背景和目标

目前市场上的大部分App都有网络功能,而网络功能大部分都是基于HTTP协议的。HTTP协议简单而又强大,这也是该协议长盛不衰的秘诀之一。每一个程序员都最好了解一下HTTP协议,这会让你受益匪浅。

回到iOS,我们将如何使用HTTP协议来完成网络请求呢?从头开始开发一个协议栈是重复造轮子,非常不明智。因为苹果的开发框架中已经包含了对HTTP协议的封装。请看官方文档说明

network.gif

背景和目标

目前市场上的大部分App都有网络功能,而网络功能大部分都是基于HTTP协议的。HTTP协议简单而又强大,这也是该协议长盛不衰的秘诀之一。每一个程序员都最好了解一下HTTP协议,这会让你受益匪浅。

回到iOS,我们将如何使用HTTP协议来完成网络请求呢?从头开始开发一个协议栈是重复造轮子,非常不明智。因为苹果的开发框架中已经包含了对HTTP协议的封装。请看官方文档说明

network.gif

上面这张图片解释了iOS框架中网络的实现方式和目前流行的网络框架所出的位置(第三行右边应该视CFHTTP,不是CFFTP))。ASI是基于CFHTTP的,AFNetworking是基于NSURL的,两者处在不同的层次上,实现原理也不同。

像这样的第三方网络框架,虽然使用起来比系统原生的简单了许多,但为了保持通用性,整个框架的接口还是比较多,调用的过程也不是一两行能搞定的。

我想,如果在开发自己的项目过程中,能不能提供一套最简单的API,只提供我们用的到的接口,并且将一些重复的处理逻辑封装起来。使得调用者在不需要了解CFHTTP、NSURL、ASIHTTPRequest、AFNetworking的情况下,只要用一两行代码,就能把网络请求处理完,让开发着处理网络请求像调用本地方法一样自然,这就是我的目标。

确定需求范围

我不想做一个通用性很强的框架,我只是希望它能解决目前的所有需求,然后越简单越好。
我们目前遇到的需求不是很多,有如下几种类型:

  • 根据本地参数,向后台发起请求,获得我要的数据。
  • 像后台提交数据,知道成功或者失败。

实现方式选择

由于个人喜好,我会基于AFNetworking之上开发一套API,同时让这套API可以方便的切换到ASIHTTPRequest或者哪天有冒出来的新框架。

最终实现

最终的实现基本满足了预期的目标,即用一两行代码完成网络的请求。下面是架构图:

http_framework.png

其实一开始设计的时候,就只有4个类:

  • MJHttpClient
  • MJRequest
  • MJHttpError
  • MJResponse

这样做满足了项目中80%的后台接口,但有些比如传图片或者需要在httpbody中塞数据的接口,用最基本的Request难以封装,所以就陆续加了两个子类。因为是见招拆招,所以并不完美,不过还是覆盖了当前项目的所有请求类型。

接下来主义介绍这几个类:

MJHttpClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef void (^MJResponseObject)(MJResponse* responseObject);
typedef void (^MJResponseError)(MJHttpError* responseError);

@interface MJHttpClient : NSObject

+ (MJHttpClient*)client;

//设置是否接受非json格式的返回
- (void)enableNoneJsonBack;

//发送请求,需要提供成功和失败的回掉
- (void)sendRequest:(MJRequest*)request
onResponse:(MJResponseObject)processBlock
onError:(MJResponseError)errorBlock;

@end

这个类非常简单,第一个方法创建自身对象,非单例(因为可能同时存在多个请求,所以不可以用单例)。第二个方法是因为后台的返回数据有些不是json格式的(事情上开发过程中前后台需要协调尽量让所有的接口返回json,不要返回字符串或者整数对象。但现在已经有了漏网之鱼,所以要做容错)。第三个方法就是发送请求的方法,就一行代码。

MJRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef NS_ENUM(NSInteger, RequestMethod) {
RequestMethod_Get,
RequestMethod_Post,
RequestMethod_Put,
RequestMethod_Delete
};

@class MJQueryModel;

@interface MJRequest : NSObject

@property (nonatomic,assign) RequestMethod method;

@property (nonatomic,copy) NSString* baseUrl;

@property (nonatomic,copy) NSString* api;

//请求参数的对象
@property (nonatomic,strong) MJQueryModel* queryModel;

//请求参数的对象(请求参数是数组的话)
@property (nonatomic,strong) NSArray* queryModels;

//创建请求
+ (MJRequest*)requestWithMethod:(RequestMethod)method api:(NSString*)api;

//请求参数对象转化成NSDictionary或者NSArray
- (id)buildParam;

@end

这个Request只能吃力url传参数的情况,body里面传参数是不支持的(这也是MJBodyRequest和MJMultipartRequest存在的必要)。但即使url传参数,也有字典和数组两种情况,所以这里加了queryModel和queryModels两个属性,在实用的时候,只需要给其中一个复制就可以了。

MJResponse

1
2
3
4
5
6
7
@interface MJResponse : NSObject

@property (nonatomic,strong) id object;

+ (MJResponse*)responseWithObject:(id)responseObject;

@end

MJResponse是请求成功之后返回的对象,可以是空、字符串、字典、数组,基本上都可以支持,在成功的block里需要对其处理。

MJHttpError

1
2
3
4
5
6
7
8
9
@interface MJHttpError : NSObject

@property (nonatomic,assign) NSInteger errorCode;

@property (nonatomic,copy) NSString* errorDescription;

+ (MJHttpError*)errorWithNSError:(NSError*)error;

@end

这个类比较鸡肋,就是一个翻版的NSError。但后期可能会对错误有特殊处理,就先占个位。

使用方法

普通请求

1
2
3
4
5
6
7
8
9
10
11
12
MJHttpClient* client = [MJHttpClient client];
MJRequest* request = [MJRequest requestWithMethod:RequestMethod_Get api:@"xxx/yyy"];

HJQueryAdsModel* model = [HJQueryAdsModel new];
model.type = [NSString stringWithFormat:@"%d",mtag];
request.queryModel = model;

[client sendRequest:request onResponse:^(MJResponse *responseObject) {
//数据处理
} onError:^(MJHttpError *responseError) {
NSLog(@"%@",responseError.errorDescription);
}];

Body请求

1
2
3
4
5
6
7
8
9
MJHttpClient* client = [MJHttpClient client];

MJBodyRequest* request = [MJBodyRequest requestWithMethod:RequestMethod_Post api:@"xxx/yyy" bodies:array];

[client sendRequest:request onResponse:^(MJResponse *responseObject) {
//数据处理
} onError:^(MJHttpError *responseError) {
NSLog(@"%@",responseError.errorDescription);
}];

Multipart请求

1
2
3
4
5
6
7
8
9
10
11
12
13
MJHttpClient* client = [MJHttpClient client];
MJMultipartData* data = [MJMultipartData new];
data.fileData = UIImagePNGRepresentation(image);
data.name = @"file";
data.fileName = @"boris.png";
data.fileType = @"image/png";

MJMultipartRequest* request = [MJMultipartRequest requestWithMethod:RequestMethod_Post api:@"xxx/yyy" datas:@[data]];
[client sendRequest:request onResponse:^(MJResponse *responseObject) {
//数据处理
} onError:^(MJHttpError *responseError) {
NSLog(@"%@",responseError.errorDescription);
}];