首页 > 代码库 > ios 多线程文件下载与上传, post数据

ios 多线程文件下载与上传, post数据

一、文件下载

获取资源文件大小有两张方式

1、

 

[objc] view plaincopy技术分享技术分享
  1. HTTP HEAD方法  
  2. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:kTimeout];  
  3. request.HTTPMethod = @"HEAD";  
  4. [NSURLConnection sendAsynchronousRequest:request queue:self.myQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {  
  5.     NSLog(@"%@", response);  
  6.     NSLog(@"---------------");  
  7.     NSLog(@"%@", data);  
  8. }];  
  9. 运行测试代码可以发现,HEAD方法只是返回资源信息,而不会返回数据体  
  10. 应用场景:  
  11. 获取资源Mimetype  
  12. 获取资源文件大小,用于端点续传或多线程下载  

2

 

 

[objc] view plaincopy技术分享技术分享
  1. 使用块代码获取网络资源大小的方法  
  2. - (void)fileSizeWithURL:(NSURL *)url completion:(void (^)(long long contentLength))completion  
  3. {  
  4.     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:kTimeout];  
  5.     request.HTTPMethod = @"HEAD";   
  6.     NSURLResponse *response = nil;  
  7.     [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];  
  8.       
  9.     completion(response.expectedContentLength);  
  10. }  


确定每次下载数据包的伪代码实现

 

 

[objc] view plaincopy技术分享技术分享
  1. - (void)downloadFileWithURL:(NSURL *)url  
  2. {  
  3.     [self fileSizeWithURL:url completion:^(long long contentLength) {  
  4.         NSLog(@"文件总大小:%lld", contentLength);          
  5.         // 根据大小下载文件  
  6.                while (contentLength > kDownloadBytes) {  
  7.             NSLog(@"每次下载长度:%lld", (long long)kDownloadBytes);  
  8.             contentLength -= kDownloadBytes;  
  9.         }  
  10.         NSLog(@"最后下载字节数:%lld", contentLength);  
  11.     }];  
  12. }  


HTTP Range的示例
通过设置Range可以指定每次从网路下载数据包的大小
Range示例
bytes=0-499 从0到499的头500个字节
bytes=500-999 从500到999的第二个500字节
bytes=500- 从500字节以后的所有字节
bytes=-500 最后500个字节
bytes=500-599,800-899 同时指定几个范围
Range小结
- 用于分隔
前面的数字表示起始字节数
后面的数组表示截止字节数,没有表示到末尾
, 用于分组,可以一次指定多个Range,不过很少用

 

 

[objc] view plaincopy技术分享技术分享
  1. 分段Range代码实现  
  2. long long fromBytes = 0;  
  3. long long toBytes = 0;  
  4. while (contentLength > kDownloadBytes) {  
  5.     toBytes = fromBytes + kDownloadBytes - 1;  
  6.     NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromBytes, toBytes];  
  7.     NSLog(@"range %@", range);  
  8.     fromBytes += kDownloadBytes;  
  9.     contentLength -= kDownloadBytes;  
  10. }  
  11. fromBytes = fromBytes + contentLength - 1;  
  12. NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromBytes, toBytes];  
  13. NSLog(@"range %@", range);  

 

[objc] view plaincopy技术分享技术分享
  1. 分段下载文件  
  2. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:kTimeout];  
  3. NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", from, end];  
  4. [request setValue:range forHTTPHeaderField:@"Range"];  
  5.   
  6. NSURLResponse *response = nil;  
  7. NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];  
  8.       
  9. NSLog(@"%@-%@-%ld", range, response, (unsigned long)data.length);  
  10. 提示:  
  11. 如果GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是200(OK)  

 

[objc] view plaincopy技术分享技术分享
  1. 将数据写入文件  
  2. // 打开缓存文件  
  3. NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.cachePath];  
  4. // 如果文件不存在,直接写入数据  
  5. if (!fp) {  
  6.     [data writeToFile:self.cachePath atomically:YES];  
  7. else {  
  8.     // 移动到文件末尾  
  9.     [fp seekToEndOfFile];  
  10.     // 将数据文件追加到文件末尾  
  11.     [fp writeData:data];  
  12.     // 关闭文件句柄  
  13.     [fp closeFile];  
  14. }  

 

[objc] view plaincopy技术分享技术分享
  1. 检查文件大小  
  2. // 判断文件是否存在  
  3. if ([[NSFileManager defaultManager] fileExistsAtPath:self.cachePath]) {  
  4.     NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.cachePath error:NULL];  
  5.     return [dict[NSFileSize] longLongValue];  
  6. else {  
  7.     return 0;  
  8. }  
  9.   
  10. 提示:由于数据是追加的,为了避免重复从网络下载文件,在下载之前  
  11. 判断缓存路径中文件是否已经存在  
  12. 如果存在检查文件大小  
  13. 如果文件大小与网络资源大小一致,则不再下载  


全部代码如下

 

 

[objc] view plaincopy技术分享技术分享
  1. //  
  2. //  MJViewController.m  
  3. //  01.文件下载  
  4. //  
  5. //  Created by apple on 14-4-29.  
  6. //  Copyright (c) 2014年 itcast. All rights reserved.  
  7. //  
  8.   
  9. #import "MJViewController.h"  
  10. #import "FileDownload.h"  
  11.   
  12. @interface MJViewController ()  
  13. @property (nonatomic, strong) FileDownload *download;  
  14. @property (weak, nonatomic) IBOutlet UIImageView *imageView;  
  15. @end  
  16.   
  17. @implementation MJViewController  
  18.   
  19. - (void)viewDidLoad  
  20. {  
  21.     [super viewDidLoad];  
  22.       
  23.     self.download = [[FileDownload alloc] init];  
  24.     [self.download downloadFileWithURL:[NSURL URLWithString:@"http://localhost/itcast/images/head4.png"] completion:^(UIImage *image) {  
  25.           
  26.         self.imageView.image = image;  
  27.     }];  
  28. }  
  29.   
  30. @end  

 

[objc] view plaincopy技术分享技术分享
  1. //  
  2. //  FileDownload.m  
  3. //  01.文件下载  
  4. //  
  5. //  Created by apple on 14-4-29.  
  6. //  Copyright (c) 2014年 itcast. All rights reserved.  
  7. //  
  8.   
  9. #import "FileDownload.h"  
  10. #import "NSString+Password.h"  
  11.   
  12. #define kTimeOut        2.0f  
  13. // 每次下载的字节数  
  14. #define kBytesPerTimes  20250  
  15.   
  16. @interface FileDownload()  
  17. @property (nonatomic, strong) NSString *cacheFile;  
  18. @property (nonatomic, strong) UIImage *cacheImage;  
  19. @end  
  20.   
  21. @implementation FileDownload  
  22. /** 
  23.  为了保证开发的简单,所有方法都不使用多线程,所有的注意力都保持在文件下载上 
  24.   
  25.  在开发中如果碰到比较绕的计算问题时,建议: 
  26.  1> 测试数据不要太大 
  27.  2> 测试数据的数值变化,能够用笔算计算出准确的数值 
  28.  3> 编写代码对照测试 
  29.  
  30.  */  
  31. //- (NSString *)cacheFile  
  32. //{  
  33. //    if (!_cacheFile) {  
  34. //        NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];  
  35. //        _cacheFile = [cacheDir stringByAppendingPathComponent:@"123.png"];  
  36. //    }  
  37. //    return _cacheFile;  
  38. //}  
  39. - (UIImage *)cacheImage  
  40. {  
  41.     if (!_cacheImage) {  
  42.         _cacheImage = [UIImage imageWithContentsOfFile:self.cacheFile];  
  43.     }  
  44.     return _cacheImage;  
  45. }  
  46.   
  47. - (void)setCacheFile:(NSString *)urlStr  
  48. {  
  49.     NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];  
  50.     urlStr = [urlStr MD5];  
  51.       
  52.     _cacheFile = [cacheDir stringByAppendingPathComponent:urlStr];  
  53. }  
  54.   
  55. - (void)downloadFileWithURL:(NSURL *)url completion:(void (^)(UIImage *image))completion  
  56. {  
  57.     // GCD中的串行队列异步方法  
  58.     dispatch_queue_t q = dispatch_queue_create("cn.itcast.download", DISPATCH_QUEUE_SERIAL);  
  59.       
  60.     dispatch_async(q, ^{  
  61.         NSLog(@"%@", [NSThread currentThread]);  
  62.           
  63.         // 把对URL进行MD5加密之后的结果当成文件名  
  64.         self.cacheFile = [url absoluteString];  
  65.           
  66.         // 1. 从网络下载文件,需要知道这个文件的大小  
  67.         long long fileSize = [self fileSizeWithURL:url];  
  68.         // 计算本地缓存文件大小  
  69.         long long cacheFileSize = [self localFileSize];  
  70.           
  71.         if (cacheFileSize == fileSize) {  
  72.             dispatch_async(dispatch_get_main_queue(), ^{  
  73.                 completion(self.cacheImage);  
  74.             });  
  75.             NSLog(@"文件已经存在");  
  76.             return;  
  77.         }  
  78.           
  79.         // 2. 确定每个数据包的大小  
  80.         long long fromB = 0;  
  81.         long long toB = 0;  
  82.         // 计算起始和结束的字节数  
  83.         while (fileSize > kBytesPerTimes) {  
  84.             // 20480 + 20480  
  85.             //  
  86.             toB = fromB + kBytesPerTimes - 1;  
  87.               
  88.             // 3. 分段下载文件  
  89.             [self downloadDataWithURL:url fromB:fromB toB:toB];  
  90.               
  91.             fileSize -= kBytesPerTimes;  
  92.             fromB += kBytesPerTimes;  
  93.         }  
  94.         [self downloadDataWithURL:url fromB:fromB toB:fromB + fileSize - 1];  
  95.   
  96.         dispatch_async(dispatch_get_main_queue(), ^{  
  97.             completion(self.cacheImage);  
  98.         });          
  99.     });  
  100. }  
  101.   
  102. #pragma mark 下载指定字节范围的数据包  
  103. /** 
  104.  NSURLRequestUseProtocolCachePolicy = 0,        // 默认的缓存策略,内存缓存 
  105.   
  106.  NSURLRequestReloadIgnoringLocalCacheData = 1,  // 忽略本地的内存缓存 
  107.  NSURLRequestReloadIgnoringCacheData 
  108.  */  
  109. - (void)downloadDataWithURL:(NSURL *)url fromB:(long long)fromB toB:(long long)toB  
  110. {  
  111.     NSLog(@"数据包:%@", [NSThread currentThread]);  
  112.       
  113.     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:kTimeOut];  
  114.       
  115.     // 指定请求中所要GET的字节范围  
  116.     NSString *range = [NSString stringWithFormat:@"Bytes=%lld-%lld", fromB, toB];  
  117.     [request setValue:range forHTTPHeaderField:@"Range"];  
  118.     NSLog(@"%@", range);  
  119.       
  120.     NSURLResponse *response = nil;  
  121.     NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];  
  122.       
  123.     // 写入文件,覆盖文件不会追加  
  124. //    [data writeToFile:@"/Users/aplle/Desktop/1.png" atomically:YES];  
  125.     [self appendData:data];  
  126.       
  127.     NSLog(@"%@", response);  
  128. }  
  129.   
  130. #pragma mark - 读取本地缓存文件大小  
  131. - (long long)localFileSize  
  132. {  
  133.     // 读取本地文件信息  
  134.     NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.cacheFile error:NULL];  
  135.     NSLog(@"%lld", [dict[NSFileSize] longLongValue]);  
  136.       
  137.     return [dict[NSFileSize] longLongValue];  
  138. }  
  139.   
  140. #pragma mark - 追加数据到文件  
  141. - (void)appendData:(NSData *)data  
  142. {  
  143.     // 判断文件是否存在  
  144.     NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.cacheFile];  
  145.     // 如果文件不存在创建文件  
  146.     if (!fp) {  
  147.         [data writeToFile:self.cacheFile atomically:YES];  
  148.     } else {  
  149.         // 如果文件已经存在追加文件  
  150.         // 1> 移动到文件末尾  
  151.         [fp seekToEndOfFile];  
  152.         // 2> 追加数据  
  153.         [fp writeData:data];  
  154.         // 3> 写入文件  
  155.         [fp closeFile];  
  156.     }  
  157. }  
  158.   
  159. #pragma mark - 获取网络文件大小  
  160. - (long long)fileSizeWithURL:(NSURL *)url  
  161. {  
  162.     // 默认是GET  
  163.     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:kTimeOut];  
  164.       
  165.     // HEAD 头,只是返回文件资源的信息,不返回具体是数据  
  166.     // 如果要获取资源的MIMEType,也必须用HEAD,否则,数据会被重复下载两次  
  167.     request.HTTPMethod = @"HEAD";  
  168.   
  169.     // 使用同步方法获取文件大小  
  170.     NSURLResponse *response = nil;  
  171.       
  172.     [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];  
  173.       
  174.     // expectedContentLength文件在网络上的大小  
  175.     NSLog(@"%lld", response.expectedContentLength);  
  176.       
  177.     return response.expectedContentLength;  
  178. }  
  179.   
  180. @end  

 

 

二、文件上传

代码如下

 

[objc] view plaincopy技术分享技术分享
  1. //  
  2. //  MJViewController.m  
  3. //  02.Post上传  
  4. //  
  5. //  Created by apple on 14-4-29.  
  6. //  Copyright (c) 2014年 itcast. All rights reserved.  
  7. //  
  8.   
  9. #import "MJViewController.h"  
  10. #import "UploadFile.h"  
  11.   
  12. @interface MJViewController ()  
  13.   
  14. @end  
  15.   
  16. @implementation MJViewController  
  17.   
  18. - (void)viewDidLoad  
  19. {  
  20.     [super viewDidLoad];  
  21.   
  22.     UploadFile *upload = [[UploadFile alloc] init];  
  23.       
  24.     NSString *urlString = @"http://localhost/upload.php";  
  25.       
  26.     NSString *path = [[NSBundle mainBundle] pathForResource:@"头像1.png" ofType:nil];  
  27.     NSData *data = [NSData dataWithContentsOfFile:path];  
  28.       
  29.     [upload uploadFileWithURL:[NSURL URLWithString:urlString] data:data];  
  30. }  
  31.   
  32. @end  

 

[objc] view plaincopy技术分享技术分享
  1. //  
  2. //  UploadFile.m  
  3. //  02.Post上传  
  4. //  
  5. //  Created by apple on 14-4-29.  
  6. //  Copyright (c) 2014年 itcast. All rights reserved.  
  7. //  
  8.   
  9. #import "UploadFile.h"  
  10.   
  11. @implementation UploadFile  
  12. // 拼接字符串  
  13. static NSString *boundaryStr = @"--";   // 分隔字符串  
  14. static NSString *randomIDStr;           // 本次上传标示字符串  
  15. static NSString *uploadID;              // 上传(php)脚本中,接收文件字段  
  16.   
  17. - (instancetype)init  
  18. {  
  19.     self = [super init];  
  20.     if (self) {  
  21.         randomIDStr = @"itcast";  
  22.         uploadID = @"uploadFile";  
  23.     }  
  24.     return self;  
  25. }  
  26.   
  27. #pragma mark - 私有方法  
  28. - (NSString *)topStringWithMimeType:(NSString *)mimeType uploadFile:(NSString *)uploadFile  
  29. {  
  30.     NSMutableString *strM = [NSMutableString string];  
  31.       
  32.     [strM appendFormat:@"%@%@\n", boundaryStr, randomIDStr];  
  33.     [strM appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\n", uploadID, uploadFile];  
  34.     [strM appendFormat:@"Content-Type: %@\n\n", mimeType];  
  35.       
  36.     NSLog(@"%@", strM);  
  37.     return [strM copy];  
  38. }  
  39.   
  40. - (NSString *)bottomString  
  41. {  
  42.     NSMutableString *strM = [NSMutableString string];  
  43.       
  44.     [strM appendFormat:@"%@%@\n", boundaryStr, randomIDStr];  
  45.     [strM appendString:@"Content-Disposition: form-data; name=\"submit\"\n\n"];  
  46.     [strM appendString:@"Submit\n"];  
  47.     [strM appendFormat:@"%@%@--\n", boundaryStr, randomIDStr];  
  48.       
  49.     NSLog(@"%@", strM);  
  50.     return [strM copy];  
  51. }  
  52.   
  53. #pragma mark - 上传文件  
  54. - (void)uploadFileWithURL:(NSURL *)url data:(NSData *)data  
  55. {  
  56.     // 1> 数据体  
  57.     NSString *topStr = [self topStringWithMimeType:@"image/png" uploadFile:@"头像1.png"];  
  58.     NSString *bottomStr = [self bottomString];  
  59.       
  60.     NSMutableData *dataM = [NSMutableData data];  
  61.     [dataM appendData:[topStr dataUsingEncoding:NSUTF8StringEncoding]];  
  62.     [dataM appendData:data];  
  63.     [dataM appendData:[bottomStr dataUsingEncoding:NSUTF8StringEncoding]];  
  64.       
  65.     // 1. Request  
  66.     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f];  
  67.       
  68.     // dataM出了作用域就会被释放,因此不用copy  
  69.     request.HTTPBody = dataM;  
  70.       
  71.     // 2> 设置Request的头属性  
  72.     request.HTTPMethod = @"POST";  
  73.       
  74.     // 3> 设置Content-Length  
  75.     NSString *strLength = [NSString stringWithFormat:@"%ld", (long)dataM.length];  
  76.     [request setValue:strLength forHTTPHeaderField:@"Content-Length"];  
  77.       
  78.     // 4> 设置Content-Type  
  79.     NSString *strContentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", randomIDStr];  
  80.     [request setValue:strContentType forHTTPHeaderField:@"Content-Type"];  
  81.       
  82.     // 3> 连接服务器发送请求  
  83.     [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {  
  84.           
  85.         NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];  
  86.         NSLog(@"%@", result);  
  87.     }];  
  88. }  
  89.   
  90.   
  91.   
  92. @end  

ios 多线程文件下载与上传, post数据