用多了ASIHttpRequest与AFNetWorking第三方网络框架难免对苹果底层的网络请求陌生,了解下苹果网络访问相关知识
一、URL Session的基本概念
1.三种工作模式: 1)默认会话模式(default):工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。 2)瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。 3)后台会话模式(background):该模式在后台完成上传和下载,在创建Configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话。 2.NSURLSession支持的三种任务 NSURLSession类支持三种类型的任务:加载数据,下载和上传。
二、相关的类
NSURLConnection这个名字,实际上指的是一组构成Foundation框架中URL加载系统的相互关联的组件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache,NSHTTPCookieStorage,NSURLCredentialStorage,以及和它同名的NSURLConnection。 在WWDC 2013中,Apple的团队对NSURLConnection进行了重构,并推出了NSURLSession作为替代。 NSURLSession也是一组相互依赖的类,它的大部分组件和NSURLConnection中的组件相同如NSURLRequest,NSURLCache等。而NSURLSession的不同之处在于,它将NSURLConnection替换为NSURLSession和NSURLSessionConfiguration,以及3个NSURLSessionTask的子类:NSURLSessionDataTask, NSURLSessionUploadTask, 和NSURLSessionDownloadTask。
自己简单封装的网络工具类 .h文件
// XMNetWorkHelper.h // Created by 修么 on 16/11/28 // Copyright © 2016年 修么. All rights reserved. // #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> typedef void (^XMCompletioBlock)(NSDictionary *dic, NSURLResponse *response, NSError *error); typedef void (^XMSuccessBlock)(NSDictionary *data); typedef void (^XMFailureBlock)(NSError *error); @interface XMNetWorkHelper : NSObject /** * get请求 */ + (void)getWithUrlString:(NSString *)url parameters:(id)parameters success:(XMSuccessBlock)successBlock failure:(XMFailureBlock)failureBlock; /** * post请求 */ + (void)postWithUrlString:(NSString *)url parameters:(id)parameters success:(XMSuccessBlock)successBlock failure:(XMFailureBlock)failureBlock;
.m文件
// // XMNetWorkHelper.m // // Created by 修么 on 16/11/28. // Copyright © 2016年 修么. All rights reserved. // #import "XMNetWorkHelper.h" @implementation XMNetWorkHelper //GET请求 + (void)getWithUrlString:(NSString *)url parameters:(id)parameters success:(XMSuccessBlock)successBlock failure:(XMFailureBlock)failureBlock { NSMutableString *mutableUrl = [[NSMutableString alloc] initWithString:url]; if ([parameters allKeys]) { [mutableUrl appendString:@"?"]; for (id key in parameters) { NSString *value = [[parameters objectForKey:key] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; [mutableUrl appendString:[NSString stringWithFormat:@"%@=%@&", key, value]]; } } NSString *urlEnCode = [[mutableUrl substringToIndex:mutableUrl.length - 1] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:urlEnCode]]; NSURLSession *urlSession = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { failureBlock(error); } else { NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; successBlock(dic); } }]; [dataTask resume]; } //POST请求 使用NSMutableURLRequest可以加入请求头 + (void)postWithUrlString:(NSString *)url parameters:(id)parameters success:(XMSuccessBlock)successBlock failure:(XMFailureBlock)failureBlock { NSURL *nsurl = [NSURL URLWithString:url]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:nsurl]; //如果想要设置网络超时的时间的话,可以使用下面的方法: //NSMutableURLRequest *mutableRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; //设置请求类型 request.HTTPMethod = @"POST"; //将需要的信息放入请求头 随便定义了几个 [request setValue:@"xxx" forHTTPHeaderField:@"Authorization"];//token [request setValue:@"xxx" forHTTPHeaderField:@"Gis-Lng"];//坐标 lng [request setValue:@"xxx" forHTTPHeaderField:@"Gis-Lat"];//坐标 lat [request setValue:@"xxx" forHTTPHeaderField:@"Version"];//版本 NSLog(@"POST-Header:%@",request.allHTTPHeaderFields); //把参数放到请求体内 NSString *postStr = [XMNetWorkHelper parseParams:parameters]; request.HTTPBody = [postStr dataUsingEncoding:NSUTF8StringEncoding]; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { //请求失败 failureBlock(error); } else { //请求成功 NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; successBlock(dic); } }]; [dataTask resume]; //开始请求 } //重新封装参数 加入app相关信息 + (NSString *)parseParams:(NSDictionary *)params { NSMutableDictionary *parameters = [[NSMutableDictionary alloc] initWithDictionary:params]; [parameters setValue:@"ios" forKey:@"client"]; [parameters setValue:@"请替换版本号" forKey:@"auth_version"]; NSString* phoneModel = @"获取手机型号" ; NSString* phoneVersion = [[UIDevice currentDevice] systemVersion];//ios系统版本号 NSString *system = [NSString stringWithFormat:@"%@(%@)",phoneModel, phoneVersion]; [parameters setValue:system forKey:@"system"]; NSDate *date = [NSDate date]; NSTimeInterval timeinterval = [date timeIntervalSince1970]; [parameters setObject:[NSString stringWithFormat:@"%.0lf",timeinterval] forKey:@"auth_timestamp"];//请求时间戳 NSString *devicetoken = @"请替换DeviceToken"; [parameters setValue:devicetoken forKey:@"uuid"] NSLog(@"请求参数:%@",parameters); NSString *keyValueFormat; NSMutableString *result = [NSMutableString new]; //实例化一个key枚举器用来存放dictionary的key //加密处理 将所有参数加密后结果当做参数传递 //parameters = @{@"i":@"加密结果 抽空加入"}; NSEnumerator *keyEnum = [parameters keyEnumerator]; id key; while (key = [keyEnum nextObject]) { keyValueFormat = [NSString stringWithFormat:@"%@=%@&", key, [params valueForKey:key]]; [result appendString:keyValueFormat]; } return result; }
NSURLSession文件下载
//下载图片 /** 该下载方式不适合大文件下载, 因为该方法需要等到文件下载完毕了, 才会回调completionHandler后面的block参数, 然后才可以在这个block参数可以获取location(文件下载缓存的路径)、response(响应)、error(错误信息)。 这样的话,对于大文件,我们就无法实时的在下载过程中获取文件的下载进度了。 @param imgUrl 图片地址 */ - (void)downloadImgWithUrl:(NSString *)imgUrl{ //1.创建会话对象 NSURLSession *session = [NSURLSession sharedSession]; //2.请求路径 NSURL *url = [NSURL URLWithString:imgUrl]; //3.创建task //接受到数据之后内部会直接写入到沙盒里面 //completionHandler location(文件下载缓存的路径)、response(响应)、error(错误信息) NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil) { //5.接受数据 NSLog(@"%@",location); //5.1确定文件的全路径 NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename]; //5.2 剪切文件 /* 第一个参数:要剪切的文件在哪里 第二个参数:目标地址 第三个参数:错误信息 */ NSError *fileError; [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:&fileError]; //打印 NSLog(@"%@---%@",fullPath,[NSThread currentThread]); if (fileError == nil) { NSLog(@"file save success"); } else { NSLog(@"file save error: %@",fileError); } } else { NSLog(@"download error:%@",error); } }]; //4.启动task [downloadTask resume]; } //下载视频 - (void)downloadVideoWithUrl:(NSString *)videoUrl{ //1.创建会话对象 NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; //2.请求路径 NSURL *url = [NSURL URLWithString:videoUrl]; //3.创建task NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url]; //4.启动task [downloadTask resume]; } #pragma mark -NSURLSessionDownloadDelegate Function // 下载数据的过程中会调用的代理方法 -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ NSLog(@"%lf",1.0 * totalBytesWritten / totalBytesExpectedToWrite); } // 重新恢复下载的代理方法 -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{ } // 写入数据到本地的时候会调用的方法 -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSString* fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];; [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil]; NSLog(@"%@",fullPath); } // 请求完成,错误调用的代理方法 -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ }
NSURLSession文件上传
//第1种方式 以流的方式上传,大小理论上不受限制,但应注意时间 -(void)uploadFileWithData:(NSData *)fileData{ // 1.创建url 服务器上传脚本 NSString *urlString = @"http://服务端/upload.php"; NSURL *url = [NSURL URLWithString:urlString]; // 2.创建请求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; // 文件上传使用post request.HTTPMethod = @"POST"; // 3.开始上传 request的body data将被忽略,而由fromData提供 [[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:fileData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil) { NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } else { NSLog(@"upload error:%@",error); } }] resume]; } //第2种方式 拼接表单的方式进行上传 - (void)uploadWithFilePath:(NSString *)filePath withfileName:(NSString *)fileName { // 1.创建url 服务器上传脚本 NSString *urlString = @"http://服务器/upload.php"; urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; NSURL *url = [NSURL URLWithString:urlString]; // 2.创建请求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; // 文件上传使用post request.HTTPMethod = @"POST"; NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",@"boundary"]; [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; // 3.拼接表单,大小受MAX_FILE_SIZE限制(2MB) FilePath:要上传的本地文件路径 formName:表单控件名称,应于服务器一致 NSData* data = [self getHttpBodyWithFilePath:filePath formName:@"file" reName:fileName]; request.HTTPBody = data; // 根据需要是否提供,非必须,如果不提供,session会自动计算 [request setValue:[NSString stringWithFormat:@"%lu",data.length] forHTTPHeaderField:@"Content-Length"]; // 4.1 使用dataTask [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil) { NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } else { NSLog(@"upload error:%@",error); } }] resume]; #if 0 // 4.2 开始上传 使用uploadTask fromData:可有可无,会被忽略 [[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:nil completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil) { NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } else { NSLog(@"upload error:%@",error); } }] resume]; #endif } /// filePath:要上传的文件路径 formName:表单控件名称 reName:上传后文件名 - (NSData *)getHttpBodyWithFilePath:(NSString *)filePath formName:(NSString *)formName reName:(NSString *)reName { NSMutableData *data = [NSMutableData data]; NSURLResponse *response = [self getLocalFileResponse:filePath]; // 文件类型:MIMEType 文件的大小:expectedContentLength 文件名字:suggestedFilename NSString *fileType = response.MIMEType; // 如果没有传入上传后文件名称,采用本地文件名! if (reName == nil) { reName = response.suggestedFilename; } // 表单拼接 NSMutableString *headerStrM =[NSMutableString string]; [headerStrM appendFormat:@"--%@\r\n",@"boundary"]; // name:表单控件名称 filename:上传文件名 [headerStrM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",formName,reName]; [headerStrM appendFormat:@"Content-Type: %@\r\n\r\n",fileType]; [data appendData:[headerStrM dataUsingEncoding:NSUTF8StringEncoding]]; // 文件内容 NSData *fileData = [NSData dataWithContentsOfFile:filePath]; [data appendData:fileData]; NSMutableString *footerStrM = [NSMutableString stringWithFormat:@"\r\n--%@--\r\n",@"boundary"]; [data appendData:[footerStrM dataUsingEncoding:NSUTF8StringEncoding]]; // NSLog(@"dataStr=%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); return data; } /// 获取响应,主要是文件类型和文件名 - (NSURLResponse *)getLocalFileResponse:(NSString *)urlString { urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; // 本地文件请求 NSURL *url = [NSURL fileURLWithPath:urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; __block NSURLResponse *localResponse = nil; // 使用信号量实现NSURLSession同步请求 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { localResponse = response; dispatch_semaphore_signal(semaphore); }] resume]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return localResponse; }
//服务端PHP脚本
//以文件流的形式上传文件 <?php /** 二进制流生成文件 * $_POST 无法解释二进制流,需要用到 $GLOBALS['HTTP_RAW_POST_DATA'] 或 php://input * $GLOBALS['HTTP_RAW_POST_DATA'] 和 php://input 都不能用于 enctype=multipart/form-data * @param String $file 要生成的文件路径 * @return boolean */ function binary_to_file($file){ $content = $GLOBALS['HTTP_RAW_POST_DATA']; // 需要php.ini设置 if(empty($content)){ $content = file_get_contents('php://input'); // 不需要php.ini设置,内存压力小 } $ret = file_put_contents($file, $content, true); return $ret; } $file_dir="images/image.png"; // 固定的文件名,注意设置images文件夹权限为所有用户可读写!!! binary_to_file($file_dir); ?> //以表单形式上传 <?php header("Content-type: application/json; charset=utf-8"); // 配置文件需要上传到服务器的路径,需要允许所有用户有可写权限,否则无法上传! $uploaddir = 'images/'; // file表单名称,应与客户端一致 $uploadfile = $uploaddir . basename($_FILES['file']['name']); move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile); echo json_encode($_FILES); ?>
NSURLSessionConfiguration
NSURLConnection是全局性的,即它的配置对全局有效,如果有两个链接需要不同的cookies、证书这些公共资源,则NSURLConnection无法满足要求,这时NSURLSession的优势则体现出来,NSURLSession可以同过NSURLSessionConfiguration可以设置全局的网络访问属性。
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; // delegateQueue:请求完成回调函数和代理函数的运行线程,如果为nil则系统自动创建一个串行队列,不影响sessionTask的运行线程 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
三种会话方式:
- defaultSessionConfiguration:进程内会话(默认会话),类似 NSURLConnection的标准配置,用硬盘来缓存数据。
- ephemeralSessionConfiguration:临时的进程内会话(内存),不会将cookie、缓存储存到本地,只会放到内存中,当应用程序退出后数据也会消失,可以用于实现“秘密浏览”
- backgroundSessionConfiguration:建立后台会话可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务,后台另起一个线程。另外,系统会根据设备的负载程度决定分配下载的资源,因此有可能会很慢甚至超时失败。
设置一些网络属性:
- HTTPAdditionalHeaders:可以设置出站请求的数据头
configuration.HTTPAdditionalHeaders = @{ @"Accept": @"application/json", @"Accept-Language": @"en", @"Authorization": authString, @"User-Agent": userAgentString };
- networkServiceType,设置网络服务类型
- NSURLNetworkServiceTypeDefault 默认
- NSURLNetworkServiceTypeVoIP VoIP
- NSURLNetworkServiceTypeVideo 视频
- NSURLNetworkServiceTypeBackground 后台
- NSURLNetworkServiceTypeVoice 语音
- allowsCellularAccess:允许蜂窝访问
- timeoutIntervalForRequest:请求的超时时长
- requestCachePolicy:缓存策略
注意事项:如果是自定义会话并指定了代理,会话会对代理进行强引用,在视图控制器销毁之前,需要取消网络会话,否则会造成内存泄漏
作者:修么
链接:http://www.jianshu.com/p/366f41a34218
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS 原生网络请求
最新推荐文章于 2024-08-26 20:18:10 发布