您的位置:首页 > 理论基础 > 计算机网络

NSURLSession-HTTP同步和异步请求

2016-12-05 20:54 183 查看
从iOS9苹果废除NSURLConnection,建议用NSURLSession

NSURLSession没有直接同步请求的方法。想使用NSURLSession进行同步请求,即数据获取后才继续执行后面代码,使用信号、信号量可以实现。

这里先贴上异步请求代码

typedef enum {
HttpRequestTypePost,
HttpRequestTypeGet
}HttpRequestType;

typedef void(^HttpSuccessBlock)(NSDictionary *successDic);
typedef void(^HttpFailedBlock)(NSError *error);

- (void)sendHttpRequestWithType:(HttpRequstType)type URL:(NSString *)url parameters:(NSDictionary *)params successBlock:(HttpSuccessBlock) successBlock faildBlock:(HttpFailedBlock) failedBlock {

NSURL *URL = [NSURL URLWithString:url];
NSMutableURLRequest *theRequest = [[NSMutableURLRequest alloc] initWithURL:URL];

if (params != nil) {
NSData *postData = [NSJSONSerialization dataWithJSONObject:params options:NSJSONWritingPrettyPrinted error:nil];
[theRequest setHTTPBody:postData];
}

NSString *methodType = (type == HttpRequstTypeGet ? @"GET" : @"POST");
[theRequest setHTTPMethod:methodType];

[theRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[theRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)@"CFBundleShortVersionString"];
NSString *iOSVersion = [[UIDevice currentDevice] systemVersion];
[theRequest setValue:[NSString stringWithFormat:@"%@/%@ ios/%@",@"sky",appVersion,iOSVersion] forHTTPHeaderField:@"User-Agent"];

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:theRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

if (error) {

if (failedBlock) {
failedBlock(error);
}

} else {
if (data) {
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];

if (successBlock) {
successBlock(dic);
}
}

}
}];

[dataTask resume];

}


其中有一步设置了session 的代理,可通过代理方法验证https请求证书

#pragma mark - https证书验证
//http请求证书不可信任,就忽略
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {

if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
/* 调用自定义的验证过程 */
if ([self wulianCustomValidation:challenge]) {
//            NSLog(@"证书可信任");
completionHandler(NSURLSessionAuthChallengeUseCredential , [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust]);
}else {
//验证证书不通过
//当执行到这的时候应该去排查一下具体为什么没过,可以看一下是否换证书了,是否是用GoDaddyRootCA或其直接签发的子证书签发的
//            NSLog(@"证书不可信任");
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil);
}

}else {

//        NSLog(@"默认处理证书");
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling , nil);

}

}

- (BOOL)wulianCustomValidation:(NSURLAuthenticationChallenge *)challenge {
//获得工程中的证书
// 获取cer格式CA证书路径
NSString *cerPath;

if (isTest) {
cerPath = [[NSBundle mainBundle] pathForResource:@"WL_test.sh.gg.root" ofType:@"cer"];

}else {
cerPath = [[NSBundle mainBundle] pathForResource:@"WL_hw.pu.sh.gg.root" ofType:@"cer"];
}

// 提取二进制内容
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
// 根据二进制内容提取证书信息
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)cerData);
// 形成钥匙链
NSArray * chain = [NSArray arrayWithObject:(__bridge id)(caRef)];
CFArrayRef caChainArrayRef = CFBridgingRetain(chain);
// 取出服务器证书
SecTrustRef trust = [[challenge protectionSpace] serverTrust];

//初始化验证结果值
SecTrustResultType trustResult = kSecTrustResultInvalid;

int err = SecTrustSetAnchorCertificates(trust, caChainArrayRef);
if (err == noErr) {
// 用CA证书验证服务器证书
err = SecTrustEvaluate(trust, &trustResult);
}

// 检查结果 kSecTrustResultConfirm 当值是这个时应询问用户许可,但这个值在iOS7.0后废弃了,系统支持到7.0 可以不管理这个值
BOOL trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));
#warning 使用根证书验证存在服务器证书如果不是我们使用的GoDaddyRootCA签发的这个子证书签发的(另一个子证书签发)也能校验过

return trusted;
}


如果不需要证书验证,可以简单的进行默认设置

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {

if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential , card);

}else {

completionHandler(NSURLSessionAuthChallengePerformDefaultHandling , nil);

}

}


在上面异步请求的基础上,封装同步请求即可,使用信号量dispatch_semaphore_t、dispatch_semaphore_signal、dispatch_semaphore_wait等待数据返回

//https同步请求
- (NSDictionary *)sendSynchronousRequestWithType:(HttpRequstType)requestType URL:(NSString *)url parameters:(NSDictionary *)params {

NSDictionary __block *resultDic;

dispatch_semaphore_t disp = dispatch_semaphore_create(0);
[self sendHttpRequestWithType:requestType URL:url parameters:params successBlock:^(NSDictionary *successDic) {

NSLog(@"正在数据加载2");
resultDic = successDic;
dispatch_semaphore_signal(disp);

} faildBlock:^(NSError *error) {

NSLog(@"正在数据加载3");
resultDic = @{@"error": error};
dispatch_semaphore_signal(disp);

4000

}];

NSLog(@"正在数据加载1");
dispatch_semaphore_wait(disp, DISPATCH_TIME_FOREVER);
NSLog(@"数据加载完毕");

return resultDic;
}

网络正常,打印的过程应该是

正在数据加载1  ->  正在数据加载2  ->  数据加载完毕
注意: 调用这个同步http请求不可以在主线程中,否则会造成卡死。需要单独开启一个线程进行数据请求
可以像下面这样

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

//进行数据请求

});


参考:
GCD中信号量的介绍  http://blog.csdn.net/eduora_meimei/article/details/23129977 
https证书验证  http://www.jianshu.com/p/31bcddf44b8d

iOS-https  http://www.jianshu.com/p/4b5d2d47833d
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iOS HTTP 同步请求