一步一步搭建自己的iOS网络请求库(一)
大家好,我是LastDay,很久没有写博客了,这周会分享一个的HTTP请求库的编写经验。
简单的介绍
介绍一下,NSURLSession是iOS7中新的网络接口,它与NSURLConnection是并列的。在程序在前台时,NSURLSession与NSURLConnection可以互为替代工作。两者在前台的时候其实没有什么区别,最主要的是程序在进入后台的时候,Background Session会更加灵活
测试NSURLSession
首先我们来新建一个工程,起名为LyHttp,在页面上放置一个按钮 ,起名为”测试”。
将按钮关联到我们的ViewController.m,起名为test。
添加以下代码
- (IBAction)test:(id)sender {
NSURL *url = [NSURL URLWithString:@"http://cityuit.sinaapp.com/1.php"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data,NSURLResponse *reponse,NSError *error){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",string);
}];
[task resume];
}
http://cityuit.sinaapp.com/1.php是我自己写的一个接口用来测试的,会返回succeed。
我们来看一下触发效果:
测试完成
接下来我们来看看我们的NSURLSession是否为异步?OK,我们来简单的修改一下我们的程序吧。讲两个阻塞,来看看我们的主线程变化。
- (IBAction)test:(id)sender {
NSURL *url = [NSURL URLWithString:@"http://cityuit.sinaapp.com/1.php"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data,NSURLResponse *reponse,NSError *error){
//阻塞3秒
[NSThread sleepForTimeInterval:3];
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",string);
}];
[task resume];
}
可以看见效果就是连续点击3次,每隔三秒进行一次输出,直到三次执行完事,这就说明我们的主线程没有被阻塞,验证说明NSURLSession为异步。
曾将看过一篇文章中,介绍过,那篇文章里介绍NSURLSession 采用的是 “异步阻塞” 模型,即所有请求在发出后都进入 2# 线程执行,在 2# 线程内部按照阻塞队列模式执行。初步判断,此阻塞特性为 [NSURLSession sharedSession] 单例模式的结果。
一步一步搭建自己的iOS网络请求库(二)
大家好,我是LastDay,上一次分享了简单体验并且测试下一下NSURLSession。
我的博客地址:http://lastday.github.io
进行简单的封装
我们接下来将要加入动态的动态的HTTP参数(parameters)的功能,之后封装出我们自己的接口。
首先呢,我们先来对上一次的代码进行一次简单的封装,建立一个新的类LyNetWork,继承于NSObject类 。新建一个静态的request方法。将请求方式和URL传入
代码如下:
+(void)request :(NSString *)method URL:(NSString *)URL{
NSURL *url = [NSURL URLWithString:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = method;
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data,NSURLResponse *reponse,NSError *error){
//NSdata转String
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",string);
}];
[task resume];
}
OK
让我们在ViewController中进行一下测试,代码如下:
@IBAction func mainButtonBeTapped(sender: AnyObject) {
[LyNetWork request:@"GET" URL:@"http://cityuit.sinaapp.com/1.php"];
}
接下来运行一下:OK 依然显示我们正确的测试结果:succeed
使用Block处理请求返回值
简单的介绍下闭包(block),对于OC来说是一个新词,但不是新的概念,不是新的东西。学过Javascript的小伙伴对闭包应该不陌生吧~学过PHP的应该也不陌生,在PHP5.3版本以后也支持闭包, 也就是OC中所提到的Block。
现在对我们的request函数进行修改,LyNetWork.m代码如下:
+(void)requestMethod:(NSString *)method
URL:(NSString *)URL
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
NSURL *url = [NSURL URLWithString:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = method;
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data,NSURLResponse *response,NSError *error){
if (error) {
failure(error);
}else{
if (success) {
success(data,response);
}
}
}];
[task resume];
}
接下来修改ViewController中的函数调用,代码修改后如下:
- (IBAction)test:(id)sender {
[LyNetWork requestMethod:@"GET"
URL:@"http://cityuit.sinaapp.com/1.php"
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",string);
}
failure:^(NSError *error){
NSLog(@"%@",error);
}];
}
测试结果依然返回succeed,测试结果正确
接下来进行我们最难处理的地方,增加parameters处理能力
我们先来处理GET方法吧
GET 方法下,params 在经过 url encode 之后直接附在 URL 末尾发送给服务器
类似于这个样子 GET /foo.php?first_name=John&last_name=Doe&action=Submit HTTP/1.1
修改我们的requestMethod方法,将其进行一下更改,引入parameters参数
+(void)requestMethod:(nullable NSString *)method
URL:(nullable NSString *)URL
parameters:(nullable id) parameters
success:(nullable void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(nullable void (^)(NSError *__nullable error))failure;
接下来就该处理我们的parameters参数了
我们从AFNetworking中借鉴一下他的处理方案(其实这里本人就是模仿编写罢了)
我们新建一个类起名为LYURLRequestSerialization,在LYURLRequestSerialization.h添加一下方法
+(NSString *)LYQueryStringFromParameters:(NSDictionary *)parameters;
进入我们的LYURLRequestSerialization.m文件中,添加以下代码:
#import "LYURLRequestSerialization.h"
@interface LYURLRequestSerialization()
@property (readwrite, nonatomic, strong) id value;
@property (readwrite, nonatomic, strong) id field;
@end
@implementation LYURLRequestSerialization
- (id)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.field = field;
self.value = value;
return self;
}
#pragma mark -
FOUNDATION_EXPORT NSArray * LYQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * LYQueryStringPairsFromKeyAndValue(NSString *key, id value);
+(NSString *)LYQueryStringFromParameters:(NSDictionary *)parameters {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (LYURLRequestSerialization *pair in LYQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * LYQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return LYQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * LYQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:LYQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:LYQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:LYQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[LYURLRequestSerialization alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
static NSString * LYPercentEscapedStringFromString(NSString *string) {
static NSString * const kLYCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kLYCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kLYCharactersGeneralDelimitersToEncode stringByAppendingString:kLYCharactersSubDelimitersToEncode]];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"
NSUInteger length = MIN(string.length - index, batchSize);
#pragma GCC diagnostic pop
NSRange range = NSMakeRange(index, length);
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return LYPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", LYPercentEscapedStringFromString([self.field description]), LYPercentEscapedStringFromString([self.value description])];
}
}
@end
简单的说一下吧,以上的方法都是为了处理传入的NSDictonary参数,因为我们在使用的时候为了方便我们传入动态的parameters,所以他的的格式是这样的:
id parmenters = @{
@"value":@"LastDays",
};
将它处理后我们希望得到的样式应该是这样,对吧?
http:URL.php?value=LastDays
这个参数是一个动态的,我们不能确定里面到底有几组参数,而且还需要考虑的一个问题就是NSDictonary中嵌套NSDictonary的情况,我们处理这种问题的一个思想就是递归。从最里面开始处理。
OK,这样我们就实现了parameters处理能力
然后我们需要测试一下
为了进行测试我又重新更改了接口,提供了参数处理的能力,以下是新的接口:
http://cityuit.sinaapp.com/1.php value=将要返回的值
更改下requestMethod方法
+(void)requestMethod:(NSString *)method
URL:(NSString *)URL
parameters:(id) parameters
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
NSString *newURL;
if ([method isEqual:@"GET"]) {
newURL = [[URL stringByAppendingString:@"?"] stringByAppendingString: [LYURLRequestSerialization LYQueryStringFromParameters:parameters]];
NSLog(@"%@",newURL);
}
NSURL *url = [NSURL URLWithString:newURL];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = method;
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data,NSURLResponse *response,NSError *error){
if (error) {
failure(error);
}else{
if (success) {
success(data,response);
}
}
}];
[task resume];
}
其中变化的地方:
if ([method isEqual:@"GET"]) {
newURL = [[URL stringByAppendingString:@"?"] stringByAppendingString: [LYURLRequestSerialization LYQueryStringFromParameters:parameters]];
NSLog(@"%@",newURL);
}
以上代码的意思就是判断一下是否未GET请求,如果是的话将处理后的parameters加到尾部,以刚才的参数为例子,处理后的newURL为:http://cityuit.sinaapp.com/1.php?value=LastDays
也就是说返回值为LastDays
到ViewController.m中进行测试,测试代码如下:
- (IBAction)test:(id)sender {
id parmenters = @{
@"value":@"LastDays",
};
[LyNetWork requestMethod:@"GET"
URL:@"http://cityuit.sinaapp.com/1.php"
parameters:parmenters
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",string);
}
failure:^(NSError *error){
NSLog(@"%@",error);
}];
}
ok返回结果为LastDays,成功添加入动态的动态的HTTP参数(parameters)的功能。
一步一步搭建自己的iOS网络请求库(三)
大家好,我是LastDay,上一次分享了简单的封装,和GET请求。这次带给大家POST方法
我的博客地址:http://lastday.github.io
POST方法
POST 下很很多协议可提供我们选择,因为我们没有写文件的上传,所以我们就采用 application/x-www-form-urlencoded 这种简单的方式发送请求吧。requestMethod 中进行一些修改,增加POST方法。
代码如下:
+(void)requestMethod:(NSString *)method
URL:(NSString *)URL
parameters:(id) parameters
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
NSString *newURL;
if ([method isEqual:@"GET"]) {
newURL = [[URL stringByAppendingString:@"?"] stringByAppendingString: [LYURLRequestSerialization LYQueryStringFromParameters:parameters]];
NSLog(@"%@",newURL);
}else{
newURL = URL;
}
NSURL *url = [NSURL URLWithString:newURL];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = method;
if([method isEqual:@"POST"]){
[request addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
request.HTTPBody =[[LYURLRequestSerialization LYQueryStringFromParameters:parameters] dataUsingEncoding:NSUTF8StringEncoding];
}
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data,NSURLResponse *response,NSError *error){
if (error) {
failure(error);
}else{
if (success) {
success(data,response);
}
}
}];
[task resume];
}
接下来我们进行测试,原来的那个接口不支持POST请求,为了测试我就新写了一个借口,有两个参数为username和password,只有当两个参数都为1的时候返回ok,其余返回no
我们给出POST请求测试代码:
- (IBAction)test:(id)sender {
NSString *URL = @"http://cityuit.sinaapp.com/p.php";
id parmenters = @{
@"username":@"1",
@"password":@"1"
};
[LyNetWork requestMethod:@"POST"
URL:URL
parameters:parmenters
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",string);
}
failure:^(NSError *error){
NSLog(@"%@",error);
}];
}
查看结果为:
若parmenters为1,222
id parmenters = @{
@"username":@"1",
@"password":@"222"
};
结果为:
一步一步搭建自己的iOS网络请求库(四)--解耦处理
对LyHttp进行降耦处理
大家好,我是LastDays,这次分享解耦处理,提高代码的可扩展性,和重用性。
我的博客地址:http://lastday.github.io
原来的那些代码虽然,看起来比较容易理解,但是最大的问题还是功能太单一,代码太杂乱了。接下来我们将是用适配器模型来实现独立于底层结构的网络API,构造我们自己真正的”库”
分析NSURLSESSION
1.构造 NSURLRequest
1.确定 URL
2.确定 HTTP 方法(GET、POST 等)
3.添加特定的 HTTP 头
4.填充 HTTP Body
2.驱动 [session dataTaskWithRequest] 方法,开始请求
开始搞
新建一个LYHTTPRequestOperationManager类,然后初始化一些参数
#import "LYHTTPRequestOperationManager.h"
#import "LYURLRequestSerialization.h"
@interface LYHTTPRequestOperationManager()
@property(nonatomic,strong) NSString *URL;
@property(nonatomic,strong) NSString *method;
@property(nonatomic,strong) NSDictionary *parameters;
@property(nonatomic,strong) NSMutableURLRequest *request;
@property(nonatomic,strong) NSURLSession *session;
@property(nonatomic,strong) NSURLSessionDataTask *task;
@end
@implementation LYHTTPRequestOperationManager
- (instancetype)initWithMethod:(NSString *)method URL:(NSString *)URL parameters:(id)parameters
{
self = [super init];
if (!self) {
return nil;
}
self.URL = URL;
self.method = method;
self.parameters = parameters;
self.request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]];
self.session = [NSURLSession sharedSession];
return self;
}
@end
看看我们刚才分析东西:
1.构造 NSURLRequest
1.确定 URL
2.确定 HTTP 方法(GET、POST 等)
3.添加特定的 HTTP 头
4.填充 HTTP Body
根据上面的分析,我们建立一个setRequest函数:
-(void)setRequest{
if ([self.method isEqual:@"GET"]&&self.parameters.count>0) {
self.request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[[self.URL stringByAppendingString:@"?"] stringByAppendingString: [LYURLRequestSerialization LYQueryStringFromParameters:self.parameters]]]];
}
self.request.HTTPMethod = self.method;
if (self.parameters.count>0) {
[self.request addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
}
setRequest完成了我们已经分析的前三个步骤
接下来创建setBody函数:
-(void)setBody{
if (self.parameters.count>0&&![self.method isEqual:@"GET"]) {
self.request.HTTPBody = [[LYURLRequestSerialization LYQueryStringFromParameters:self.parameters] dataUsingEncoding:NSUTF8StringEncoding];
}
}
setBody就是完成了我们最后一个步骤
以上处理完成了构造 NSURLREQUEST
我们该进行驱动 [SESSION DATATASKWITHREQUEST] 方法,开始请求
-(void)driveTask:(void(^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
[self setRequest];
[self setBody];
[self setTaskWithSuccess:success failure:failure];
}
采用适配器模式封装LyHttp
简单的来介绍一下适配器的概念:
适配器模式,可以这么说,用于连接两种不同种类的对象,使其毫无问题地协同工作。思想其实非常的简单。适配器实现客户端需要的某种接口的行为。同时它又连接到另一个具有不同行为的对象。一边是客户端懂得如何使用的目标接口,另一边还客户端一无所知的适配者,适配器站在两者中间。适配器主要的作用是把适配者的行为传递给管道另一端的客户端,其实这种所谓的很高大赏的适配器模型很多人其实已经使用过了
接下来就是来修改我们的LyNetWork
封装多级API
不带parameters:
+(void)requestWithMethod:(NSString *)method
URL:(NSString *)URL
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
LYHTTPRequestOperationManager *manange = [[LYHTTPRequestOperationManager alloc] initWithMethod:method URL:URL parameters:nil];
[manange driveTask:success failure:failure];
}
不带parameters的GET方法:
+(void)requestGetWithURL:(NSString *)URL
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
LYHTTPRequestOperationManager *manange = [[LYHTTPRequestOperationManager alloc] initWithMethod:@"GET" URL:URL parameters:nil];
[manange driveTask:success failure:failure];
}
带parameters的GET方法:
//GET带parameters
+(void)requestGetWithURL:(NSString *)URL
parameters:(id) parameters
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
LYHTTPRequestOperationManager *manange = [[LYHTTPRequestOperationManager alloc] initWithMethod:@"GET" URL:URL parameters:parameters];
[manange driveTask:success failure:failure];
}
POST不带parameters
+(void)requestPostWithURL:(NSString *)URL
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
LYHTTPRequestOperationManager *manange = [[LYHTTPRequestOperationManager alloc] initWithMethod:@"POST" URL:URL parameters:nil];
[manange driveTask:success failure:failure];
}
POST带parameters
+(void)requestPostWithURL:(NSString *)URL
parameters:(id) parameters
success:(void (^)(NSData *__nullable data,NSURLResponse * __nullable response))success
failure:(void (^)(NSError *__nullable error))failure
{
LYHTTPRequestOperationManager *manange = [[LYHTTPRequestOperationManager alloc] initWithMethod:@"POST" URL:URL parameters:parameters];
[manange driveTask:success failure:failure];
}
接下来就是进入我们的修改ViewController代码进行测试:
- (IBAction)test:(id)sender {
NSString *postURL = @"http://cityuit.sinaapp.com/p.php";
NSString *getURL= @"http://cityuit.sinaapp.com/1.php";
id parmentersPost = @{
@"username":@"1",
@"password":@"1"
};
id parmentersGet = @{
@"value":@"Lastday",
};
[LyNetWork requestWithMethod:@"POST"
URL:@"http://cityuit.sinaapp.com/1.php?value=Lastday"
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"requestWithMethod = %@",string);
}
failure:^(NSError *error){
NSLog(@"====%@",error);
}];
[LyNetWork requestPostWithURL:postURL
parameters:parmentersPost
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"requestPostWithURL(带参数) = %@",string);
}
failure:^(NSError *error){
}];
[LyNetWork requestPostWithURL:postURL
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"requestPostWithURL(不带参数) = %@",string);
}
failure:^(NSError *error){
}];
[LyNetWork requestGetWithURL:getURL
parameters:parmentersGet
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"requestGetWithURL(带参数) = %@",string);
}
failure:^(NSError *error){
}];
[LyNetWork requestGetWithURL:getURL
success:^(NSData *data,NSURLResponse *response){
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"requestGetWithURL(不带参数) = %@",string);
}
failure:^(NSError *error){
}];
}
看一下执行结果