网络编程(二)NSURLConnection 实现文件下载上传

一、简单下载
 
NSURLConnection 的同步下载和 Block 异步下载只能一次下载全部数据,这对于数据量较大的文件都是不适合的,NSURLConnection 的代理异步下载方法可以实现数据的拼接,所以,这里选择 NSURLConnection 的代理方法下载大文件:
 
 1 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
 2 {
 3      //拼接接收到的数据
 4     [self.fileData appendData: data];
 5 }
 6  
 7 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
 8 {
 9    
10     //将文件放入沙盒中,这里选择放入caches
11     NSString *caches = [NSSearchPathForDirectoriesInDomains( NSCachesDirectory, NSUserDomainMask, YES) lastObject];
12     NSString *filePath = [caches stringByAppendingPathComponent: @"bigDataFile.zip"];
13     [self.fileData writeToFile: filePath atomically: YES];
14  
15 }

 

 
这里有几点需要注意:
 
  • 大数据量的文件应该放到 tmp 或 Caches 中,因为 Documents,Library/Preferences 都会自动备份到服务器,而 tmp 和 Caches 的区别就是前者会被随机删除后者不会
  • 这里有2处效率很低,一是代码拼接出,二是将文件写入沙河,这里把大数据都放在了内存中,如果数据太大就会太占用内存甚至挤爆内存
 
二、这里使用 NSFileHandle 来处理数据:
 
 1 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
 2 {
 3    
 4     //文件的存储路径
 5     NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
 6     NSString *filepath = [caches stringByAppendingPathComponent:@"文件.zip"];
 7    
 8     //再文件存储路径中创建一个空的文件夹用于放置数据
 9     NSFileManager *mgr = [NSFileManager defaultManager];
10     [mgr createFileAtPath:filepath contents:nil attributes:nil];
11    
12     //创建写数据的文件句柄
13     self.handle = [NSFileHandle fileHandleForWritingAtPath:filepath];
14    
15     // 3.获得完整文件的长度
16     self.totalLength = response.expectedContentLength;
17 }
18  
19 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
20 {
21    
22     // 每次开始写入文件时将 NSFileHandle 移动到文件的尾部
23     [self.handle seekToEndOfFile];
24     // 从当前移动的位置(文件尾部)开始写入数据
25     [self.handle writeData:data];
26 }
27  
28 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
29 {
30     // 记住最后不再使用时要关闭连接(不再输入数据到文件中)
31     [self.handle closeFile];
32     self.handle = nil;
33 }

 

 
这种方法实现了接收到数据的同时就将数据写入沙盒中,不在内存中停留,可以添加一个 UIProgressView 来显示下载的进度:
 
 1 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
 2 {
 3      //可以在响应头中获得文件完整的长度
 4     self.totalLength = response.expectedContentLength;
 5 }
 6  
 7 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
 8 {
 9     // 累加每一次获取的数据的长度
10     self.currentLength += data.length;
11    
12     // 比较已下载数据和总数据长度,显示进度
13     double progress = (double)self.currentLength / self.totalLength;
14     self.progressView.progress = progress;
15 }
16  
17 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
18 { 
19     // 清空属性值
20     self.currentLength = 0;
21     self.totalLength = 0;
22 }

 

 
三、下载的暂停和恢复
 
注意,NSURLConnection 请求网络若中途暂停,想要重新开始必须重新创建一个 NSURLConnection 对象去执行
 
 1 -(void) startDownloading: (UIButton *) button {
 2    
 3     //根据是否正在下载改变按钮文字
 4     if (self.isDownloading) { // 暂停下载
 5        
 6         self.downloading = NO;
 7        
 8         [button setTitle:@"开始下载文件" forState:UIControlStateNormal];
 9        
10         // 取消当前的请求
11         [self.connection cancel];
12         self.connection = nil;
13        
14     } else { // 开始下载
15        
16         self.downloading = YES;
17        
18         [button setTitle:@"暂停下载文件" forState:UIControlStateNormal];
19        
20         NSURL *url = [NSURL URLWithString:@"文件.zip"];
21         NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
22        
23         // 设置请求头信息,从上次停止的地方开始下载数据
24         NSString *value = [NSString stringWithFormat:@"bytes=%lld-", self.currentLength];
25         //设置下载范围
26         [request setValue:value forHTTPHeaderField:@"Range"];
27        
28         self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
29     }
30    
31 }

 

 
这时候需要判断是否是重新恢复下载的:
 
1 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
2 {
3     if (self.totalLength) return;
4 }
5  

 

这里的 request 设置了数据的请求范围 Range,设置方法是:
 
  • - 符号用于分隔 前面的数字表示起始字节数 ,后面的数组表示截止字节数,没有则表示到末尾
  • ,符号用于分组,可以一次指定多个Range
 
四、压缩文件的处理
 
使用第三方框架 SSZipArchive 解压
  • 引入 libz.dylib 框架:
 
技术分享
 
  • 导入类库:
 
技术分享
 
  • 解压文件:
 
#import "SSZipArchive.h"
 
1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2                
3       [SSZipArchive unzipFileAtPath: 文件路径 toDestination: 解压位置];
4 });

 

 
  • 压缩文件:
 
1     // 获得 bundle 中所有的png图片路径
2     NSArray *files = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:nil];
3    
4     // 压缩文件需要存放的路径
5     NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
6     NSString *zipFilepath = [caches stringByAppendingPathComponent:@"pngs.zip"];
7    
8     // 压缩文件为 zip
9     [SSZipArchive createZipFileAtPath:zipFilepath withFilesAtPaths:files];

 

 
五、使用 POST 上传文件
 
要上传文件必须使用 POST 请求,使用 NSURLConnection 实现文件上传实在不是什么好办法,参数太多且容易出错,所以一般开发中都是使用第三方框架实现,下面以上传一个 JSON 文件为例,说明如何实现文件上传:
 
 
   
 1  // 创建请求
 2     NSURL *url = [NSURL URLWithString:@"http://192.168.1.200:8080/MJServer/order"];
 3     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
 4     request.HTTPMethod = @"POST";
 5    
 6     // 设置请求头
 7     [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
 8    
 9     // 设置请求体
10     NSDictionary *json = @{ @"username" : @"jack",@"passwd" : @"789",@"age" : @"13"};
11    
12     //将字典转换成 json 数据
13     NSData *data = [NSJSONSerialization dataWithJSONObject:json options:NSJSONWritingPrettyPrinted error:nil];
14     request.HTTPBody = data;
15    
16     // 发送请求
17     [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
18         
19     }];

 

 
简单来说就是:
 
  • 创建请求
  • 设置请求头
  • 设置请求体
  • 发送请求
 
 
 
 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。