NSURLSession请求网络数据

在平时开发中一直使用的是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大神啊!

你可能感兴趣的:(NSURLSession请求网络数据)