在平时开发中一直使用的是AFNetworking,时间久了感觉苹果原生的NSURLSession都不会用了,这篇文章简单记录下NSURLSession进行网络数据请求,方便以后查阅复习之用。
一、构造NSMutableURLRequest
Request可以设置网络请求的URI、请求头、请求正文(POST请求时)。GET请求时需将参数直接拼接在URI后面,POST请求时需要将参数作为NSData数据放在请求Body中。request可以灵活的设置请求方法HTTPMethod ,请求URL,请求发送数据格式Content-Type等消息。
// 可以在此处拼接公共url
NSString *strUrl = [NSString stringWithFormat:@"%@",urlStr];
// 将URL中的空白全部去掉,防止url前后有空白
NSString *urlPath = [strUrl stringByReplacingOccurrencesOfString:@" " withString:@""];
NSURL *url = [NSURL URLWithString:urlPath];
// 构造request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.timeoutInterval = 30;
NSString *methodUpperCase = [method uppercaseString];
// 参数拼接
NSMutableString *paramsString = [[NSMutableString alloc] init];
// 取出参数中所有的key值
NSArray *allKeysFromDic = params.allKeys;
for (int i =0 ; i < params.count; i++)
{
NSString *key = allKeysFromDic[i];
NSString *value = params[key];
[paramsString appendFormat:@"%@=%@",key,value];
// 每个关键字之间都用&隔开
if (i < params.count - 1)
{
[paramsString appendString:@"&"];
}
}
if ([methodUpperCase isEqualToString:@"GET"]) // get请求就只有URL
{
// GET请求需要拼接
NSString *separe = url.query ? @"&" : @"";
NSString *paramsURL = [NSString stringWithFormat:@"%@%@%@",urlPath,separe,paramsString];
request.URL = [NSURL URLWithString:paramsURL];
request.HTTPMethod = @"GET";
}
else if ([methodUpperCase isEqualToString:@"POST"]) // POST请求就需要设置请求正文
{
NSData *bodyData = [paramsString dataUsingEncoding:NSUTF8StringEncoding];
request.URL = url;
request.HTTPBody = bodyData;
request.HTTPMethod = @"POST";
}
else // 暂时没有考虑DELETE PUT等其他方式
{
return;
}
// 设置请求数据的content-type
// [reuqest setValue:contentType forHTTPHeaderField:@"Content-Type"];
经常有后台要求移动端设置请求头数据格式,就是在此步完成的啦。
二、NSURLSessionConfiguration设置
NSURLSessionConfiguration可以设置请求超时时间、请求头信息(如设置希望服务器返回数据格式信息等)、缓存策略、是否允许使用流量等信息。
// NSURLSessionConfiguration 设置请求超时时间, 请求头等信息
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 设置请求超时为30秒钟
configuration.timeoutIntervalForRequest = 30;
// 设置在流量情况下是否继续请求
configuration.allowsCellularAccess = YES;
// 设置请求的header
configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json,text/html,text/plain",
@"Accept-Language": @"en"};
三、创建NSURLSession
好像现在苹果比较新的API中,都会引入Session这个词, 有点管理、关联的意思在里面。
创建NSURLSession,设置代理,并发送网络请求。在代理方法中可以设置HTTPS时证书相关策略,方便监听下载数据时的进度条。
// 创建NSURLSession
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
// 发送Request
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error)
{
fail(error,@"没有网络");
return;
}
NSError *err = nil;
NSHTTPURLResponse *http = (NSHTTPURLResponse*)response;
NSLog(@"%@",http.MIMEType);
/*
1、 服务端需要返回一段普通文本给客户端,Content-Type="text/plain"
2 、服务端需要返回一段HTML代码给客户端 ,Content-Type="text/html"
3 、服务端需要返回一段XML代码给客户端 ,Content-Type="text/xml"
4 、服务端需要返回一段javascript代码给客户端,text/javascript
5 、服务端需要返回一段json串给客户端,application/Json
*/
if ([http.MIMEType isEqualToString:@"text/html"])
{
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
succ(html);
}
else
{
id res = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&err];
if (err)
{
fail(err, @"数据解析错误");
return;
}
succ(res);
}
}];
[task resume]; // 开始网络请求
在NSURLSessionDelegate中可以设置HTTPS请求是的证书使用策略情况
// 只有HTTPS请求才会回调这个函数
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler
{
NSLog(@"didReceiveChallenge ");
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) // 服务器信任证书
{
NSLog(@"server ---------");
NSString *host = challenge.protectionSpace.host;
NSLog(@"%@", host);
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
else if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) // 输入密码的证书,校验自己的证书
{
//客户端证书认证
//TODO:设置客户端证书认证
// load cert
NSLog(@"client");
NSString *path = [[NSBundle mainBundle]pathForResource:@"client"ofType:@"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path];
CFDataRef inP12data = (__bridge CFDataRef)p12data;
SecIdentityRef myIdentity;
OSStatus status = [self extractIdentity:inP12data toIdentity:&myIdentity];
if (status != 0)
{
return;
}
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = { myCertificate };
CFArrayRef certsArray =CFArrayCreate(NULL, certs,1,NULL);
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCredentialPersistencePermanent];
// 网上很多错误代码如上,正确的为:
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
}
- (OSStatus)extractIdentity:(CFDataRef)inP12Data toIdentity:(SecIdentityRef*)identity {
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("123456");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12Data, options, &items);
if (securityError == 0)
{
CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
}
else
{
NSLog(@"clinet.p12 error!");
}
if (options)
{
CFRelease(options);
}
return securityError;
}
总结:NSURLRequest更像一个模型,只负责传递URL、请求方法、请求参数、请求Content-Type等数据;NSURLSessionConfiguration则偏重于设置缓存机制,已经网络使用情况的配置;NSURLSession主要负责数据的处理过程,方便在其代理方法中进行各种操作;NSURLSessionDataTask则管理网络请求的生命周期。
将Demo上传到Github。
AFNetworking基本也是按上面的方法实现的,只是代码写的更加规范、更加健壮和通用。自己写一次NSURLSession之后,再看一次AFNetworking的源码,感觉对其理解更进一步,佩服Matt大神啊!