背景和目标
目前市场上的大部分App都有网络功能,而网络功能大部分都是基于HTTP协议的。HTTP协议简单而又强大,这也是该协议长盛不衰的秘诀之一。每一个程序员都最好了解一下HTTP协议,这会让你受益匪浅。
回到iOS,我们将如何使用HTTP协议来完成网络请求呢?从头开始开发一个协议栈是重复造轮子,非常不明智。因为苹果的开发框架中已经包含了对HTTP协议的封装。请看官方文档说明
背景和目标
目前市场上的大部分App都有网络功能,而网络功能大部分都是基于HTTP协议的。HTTP协议简单而又强大,这也是该协议长盛不衰的秘诀之一。每一个程序员都最好了解一下HTTP协议,这会让你受益匪浅。
回到iOS,我们将如何使用HTTP协议来完成网络请求呢?从头开始开发一个协议栈是重复造轮子,非常不明智。因为苹果的开发框架中已经包含了对HTTP协议的封装。请看官方文档说明
上面这张图片解释了iOS框架中网络的实现方式和目前流行的网络框架所出的位置(第三行右边应该视CFHTTP,不是CFFTP))。ASI是基于CFHTTP的,AFNetworking是基于NSURL的,两者处在不同的层次上,实现原理也不同。
像这样的第三方网络框架,虽然使用起来比系统原生的简单了许多,但为了保持通用性,整个框架的接口还是比较多,调用的过程也不是一两行能搞定的。
我想,如果在开发自己的项目过程中,能不能提供一套最简单的API,只提供我们用的到的接口,并且将一些重复的处理逻辑封装起来。使得调用者在不需要了解CFHTTP、NSURL、ASIHTTPRequest、AFNetworking的情况下,只要用一两行代码,就能把网络请求处理完,让开发着处理网络请求像调用本地方法一样自然,这就是我的目标。
确定需求范围
我不想做一个通用性很强的框架,我只是希望它能解决目前的所有需求,然后越简单越好。
我们目前遇到的需求不是很多,有如下几种类型:
- 根据本地参数,向后台发起请求,获得我要的数据。
- 像后台提交数据,知道成功或者失败。
实现方式选择
由于个人喜好,我会基于AFNetworking之上开发一套API,同时让这套API可以方便的切换到ASIHTTPRequest或者哪天有冒出来的新框架。
最终实现
最终的实现基本满足了预期的目标,即用一两行代码完成网络的请求。下面是架构图:
其实一开始设计的时候,就只有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); }];
|