获取资源文件大小有两张方式
1、
HTTP HEAD方法 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:kTimeout]; request.HTTPMethod = @"HEAD"; [NSURLConnection sendAsynchronousRequest:request queue:self.myQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { NSLog(@"%@", response); NSLog(@"---------------"); NSLog(@"%@", data); }]; 运行测试代码可以发现,HEAD方法只是返回资源信息,而不会返回数据体 应用场景: 获取资源Mimetype 获取资源文件大小,用于端点续传或多线程下载2
使用块代码获取网络资源大小的方法 - (void)fileSizeWithURL:(NSURL *)url completion:(void (^)(long long contentLength))completion { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:kTimeout]; request.HTTPMethod = @"HEAD"; NSURLResponse *response = nil; [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL]; completion(response.expectedContentLength); }
确定每次下载数据包的伪代码实现
- (void)downloadFileWithURL:(NSURL *)url { [self fileSizeWithURL:url completion:^(long long contentLength) { NSLog(@"文件总大小:%lld", contentLength); // 根据大小下载文件 while (contentLength > kDownloadBytes) { NSLog(@"每次下载长度:%lld", (long long)kDownloadBytes); contentLength -= kDownloadBytes; } NSLog(@"最后下载字节数:%lld", contentLength); }]; }
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,不过很少用
分段Range代码实现 long long fromBytes = 0; long long toBytes = 0; while (contentLength > kDownloadBytes) { toBytes = fromBytes + kDownloadBytes - 1; NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromBytes, toBytes]; NSLog(@"range %@", range); fromBytes += kDownloadBytes; contentLength -= kDownloadBytes; } fromBytes = fromBytes + contentLength - 1; NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromBytes, toBytes]; NSLog(@"range %@", range);
分段下载文件 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:kTimeout]; NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", from, end]; [request setValue:range forHTTPHeaderField:@"Range"]; NSURLResponse *response = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL]; NSLog(@"%@-%@-%ld", range, response, (unsigned long)data.length); 提示: 如果GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是200(OK)
将数据写入文件 // 打开缓存文件 NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.cachePath]; // 如果文件不存在,直接写入数据 if (!fp) { [data writeToFile:self.cachePath atomically:YES]; } else { // 移动到文件末尾 [fp seekToEndOfFile]; // 将数据文件追加到文件末尾 [fp writeData:data]; // 关闭文件句柄 [fp closeFile]; }
检查文件大小 // 判断文件是否存在 if ([[NSFileManager defaultManager] fileExistsAtPath:self.cachePath]) { NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.cachePath error:NULL]; return [dict[NSFileSize] longLongValue]; } else { return 0; } 提示:由于数据是追加的,为了避免重复从网络下载文件,在下载之前 判断缓存路径中文件是否已经存在 如果存在检查文件大小 如果文件大小与网络资源大小一致,则不再下载
全部代码如下
// // MJViewController.m // 01.文件下载 // // Created by apple on 14-4-29. // Copyright (c) 2014年 itcast. All rights reserved. // #import "MJViewController.h" #import "FileDownload.h" @interface MJViewController () @property (nonatomic, strong) FileDownload *download; @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation MJViewController - (void)viewDidLoad { [super viewDidLoad]; self.download = [[FileDownload alloc] init]; [self.download downloadFileWithURL:[NSURL URLWithString:@"http://localhost/itcast/images/head4.png"] completion:^(UIImage *image) { self.imageView.image = image; }]; } @end
// // FileDownload.m // 01.文件下载 // // Created by apple on 14-4-29. // Copyright (c) 2014年 itcast. All rights reserved. // #import "FileDownload.h" #import "NSString+Password.h" #define kTimeOut 2.0f // 每次下载的字节数 #define kBytesPerTimes 20250 @interface FileDownload() @property (nonatomic, strong) NSString *cacheFile; @property (nonatomic, strong) UIImage *cacheImage; @end @implementation FileDownload /** 为了保证开发的简单,所有方法都不使用多线程,所有的注意力都保持在文件下载上 在开发中如果碰到比较绕的计算问题时,建议: 1> 测试数据不要太大 2> 测试数据的数值变化,能够用笔算计算出准确的数值 3> 编写代码对照测试 */ //- (NSString *)cacheFile //{ // if (!_cacheFile) { // NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; // _cacheFile = [cacheDir stringByAppendingPathComponent:@"123.png"]; // } // return _cacheFile; //} - (UIImage *)cacheImage { if (!_cacheImage) { _cacheImage = [UIImage imageWithContentsOfFile:self.cacheFile]; } return _cacheImage; } - (void)setCacheFile:(NSString *)urlStr { NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; urlStr = [urlStr MD5]; _cacheFile = [cacheDir stringByAppendingPathComponent:urlStr]; } - (void)downloadFileWithURL:(NSURL *)url completion:(void (^)(UIImage *image))completion { // GCD中的串行队列异步方法 dispatch_queue_t q = dispatch_queue_create("cn.itcast.download", DISPATCH_QUEUE_SERIAL); dispatch_async(q, ^{ NSLog(@"%@", [NSThread currentThread]); // 把对URL进行MD5加密之后的结果当成文件名 self.cacheFile = [url absoluteString]; // 1. 从网络下载文件,需要知道这个文件的大小 long long fileSize = [self fileSizeWithURL:url]; // 计算本地缓存文件大小 long long cacheFileSize = [self localFileSize]; if (cacheFileSize == fileSize) { dispatch_async(dispatch_get_main_queue(), ^{ completion(self.cacheImage); }); NSLog(@"文件已经存在"); return; } // 2. 确定每个数据包的大小 long long fromB = 0; long long toB = 0; // 计算起始和结束的字节数 while (fileSize > kBytesPerTimes) { // 20480 + 20480 // toB = fromB + kBytesPerTimes - 1; // 3. 分段下载文件 [self downloadDataWithURL:url fromB:fromB toB:toB]; fileSize -= kBytesPerTimes; fromB += kBytesPerTimes; } [self downloadDataWithURL:url fromB:fromB toB:fromB + fileSize - 1]; dispatch_async(dispatch_get_main_queue(), ^{ completion(self.cacheImage); }); }); } #pragma mark 下载指定字节范围的数据包 /** NSURLRequestUseProtocolCachePolicy = 0, // 默认的缓存策略,内存缓存 NSURLRequestReloadIgnoringLocalCacheData = 1, // 忽略本地的内存缓存 NSURLRequestReloadIgnoringCacheData */ - (void)downloadDataWithURL:(NSURL *)url fromB:(long long)fromB toB:(long long)toB { NSLog(@"数据包:%@", [NSThread currentThread]); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:kTimeOut]; // 指定请求中所要GET的字节范围 NSString *range = [NSString stringWithFormat:@"Bytes=%lld-%lld", fromB, toB]; [request setValue:range forHTTPHeaderField:@"Range"]; NSLog(@"%@", range); NSURLResponse *response = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL]; // 写入文件,覆盖文件不会追加 // [data writeToFile:@"/Users/aplle/Desktop/1.png" atomically:YES]; [self appendData:data]; NSLog(@"%@", response); } #pragma mark - 读取本地缓存文件大小 - (long long)localFileSize { // 读取本地文件信息 NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.cacheFile error:NULL]; NSLog(@"%lld", [dict[NSFileSize] longLongValue]); return [dict[NSFileSize] longLongValue]; } #pragma mark - 追加数据到文件 - (void)appendData:(NSData *)data { // 判断文件是否存在 NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.cacheFile]; // 如果文件不存在创建文件 if (!fp) { [data writeToFile:self.cacheFile atomically:YES]; } else { // 如果文件已经存在追加文件 // 1> 移动到文件末尾 [fp seekToEndOfFile]; // 2> 追加数据 [fp writeData:data]; // 3> 写入文件 [fp closeFile]; } } #pragma mark - 获取网络文件大小 - (long long)fileSizeWithURL:(NSURL *)url { // 默认是GET NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:kTimeOut]; // HEAD 头,只是返回文件资源的信息,不返回具体是数据 // 如果要获取资源的MIMEType,也必须用HEAD,否则,数据会被重复下载两次 request.HTTPMethod = @"HEAD"; // 使用同步方法获取文件大小 NSURLResponse *response = nil; [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL]; // expectedContentLength文件在网络上的大小 NSLog(@"%lld", response.expectedContentLength); return response.expectedContentLength; } @end
代码如下
// // MJViewController.m // 02.Post上传 // // Created by apple on 14-4-29. // Copyright (c) 2014年 itcast. All rights reserved. // #import "MJViewController.h" #import "UploadFile.h" @interface MJViewController () @end @implementation MJViewController - (void)viewDidLoad { [super viewDidLoad]; UploadFile *upload = [[UploadFile alloc] init]; NSString *urlString = @"http://localhost/upload.php"; NSString *path = [[NSBundle mainBundle] pathForResource:@"头像1.png" ofType:nil]; NSData *data = [NSData dataWithContentsOfFile:path]; [upload uploadFileWithURL:[NSURL URLWithString:urlString] data:data]; } @end
// // UploadFile.m // 02.Post上传 // // Created by apple on 14-4-29. // Copyright (c) 2014年 itcast. All rights reserved. // #import "UploadFile.h" @implementation UploadFile // 拼接字符串 static NSString *boundaryStr = @"--"; // 分隔字符串 static NSString *randomIDStr; // 本次上传标示字符串 static NSString *uploadID; // 上传(php)脚本中,接收文件字段 - (instancetype)init { self = [super init]; if (self) { randomIDStr = @"itcast"; uploadID = @"uploadFile"; } return self; } #pragma mark - 私有方法 - (NSString *)topStringWithMimeType:(NSString *)mimeType uploadFile:(NSString *)uploadFile { NSMutableString *strM = [NSMutableString string]; [strM appendFormat:@"%@%@\n", boundaryStr, randomIDStr]; [strM appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\n", uploadID, uploadFile]; [strM appendFormat:@"Content-Type: %@\n\n", mimeType]; NSLog(@"%@", strM); return [strM copy]; } - (NSString *)bottomString { NSMutableString *strM = [NSMutableString string]; [strM appendFormat:@"%@%@\n", boundaryStr, randomIDStr]; [strM appendString:@"Content-Disposition: form-data; name=\"submit\"\n\n"]; [strM appendString:@"Submit\n"]; [strM appendFormat:@"%@%@--\n", boundaryStr, randomIDStr]; NSLog(@"%@", strM); return [strM copy]; } #pragma mark - 上传文件 - (void)uploadFileWithURL:(NSURL *)url data:(NSData *)data { // 1> 数据体 NSString *topStr = [self topStringWithMimeType:@"image/png" uploadFile:@"头像1.png"]; NSString *bottomStr = [self bottomString]; NSMutableData *dataM = [NSMutableData data]; [dataM appendData:[topStr dataUsingEncoding:NSUTF8StringEncoding]]; [dataM appendData:data]; [dataM appendData:[bottomStr dataUsingEncoding:NSUTF8StringEncoding]]; // 1. Request NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f]; // dataM出了作用域就会被释放,因此不用copy request.HTTPBody = dataM; // 2> 设置Request的头属性 request.HTTPMethod = @"POST"; // 3> 设置Content-Length NSString *strLength = [NSString stringWithFormat:@"%ld", (long)dataM.length]; [request setValue:strLength forHTTPHeaderField:@"Content-Length"]; // 4> 设置Content-Type NSString *strContentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", randomIDStr]; [request setValue:strContentType forHTTPHeaderField:@"Content-Type"]; // 3> 连接服务器发送请求 [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@", result); }]; } @end