1.使用工具类,提供实例方法供外部调用
#import <Foundation/Foundation.h>
@interface QKFileDownload : NSObject
- (void)downloadImageWithURL:(NSURL *)url;
@end
2.实现方法: - (void)downloadImageWithURL:(NSURL *)url
2.1要实现文件的分段下载,得先知道欲下载文件的大小,然后把大小切割成N份
获取欲下载的文件的大小:
long long netImageSize = [self netImageSizeWithURL:url];
实现私有方法 netImageSizeWithURL:url
// 计算网络图片的大小
- (long long) netImageSizeWithURL:(NSURL *)url
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f];
request.HTTPMethod = @"HEAD";
NSURLResponse *response = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
NSLog(@"length = %lld",response.expectedContentLength);
return response.expectedContentLength;
}
2.2完善 - ( void )downloadImageWithURL:( NSURL *)url 方法
long long fromByte = 0;
long long toByte = 0;
while (netImageSize > kPerByte) {
toByte = fromByte + (kPerByte - 1);
[self downloadWithURL:url from:fromByte to:toByte];
NSLog(@"%lld-%lld",fromByte,toByte);
fromByte += kPerByte;
netImageSize -= kPerByte;
}
// 下载剩余的
[self downloadWithURL:url from:fromByte to:fromByte + netImageSize - 1];
2.3实现 downloadWithURL :url from :fromByte to :toByte 方法
- (void) downloadWithURL:(NSURL *)url from:(long long)fromByte to:(long long)toByte
{
// 确定request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:2.0f];
NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld",fromByte,toByte];
[request setValue:range forHTTPHeaderField:@"Range"];
// 返回每一段Range的Data
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
// 追加数据
[self append:data];
}
2.4.1 实现 append:data 方法
// 追加数据
- (void)append:(NSData *)data
{
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.cacheFile];
if (!handle) { // 不存在cacheFile,创建文件
[data writeToFile:self.cacheFile atomically:YES];
}else{ // 存在cacheFile,追加数据
[handle seekToEndOfFile];
[handle writeData:data];
[handle closeFile];
}
}
2.4.2 实现cacheFile的get方法
- (NSString *)cacheFile
{
if (!_cacheFile) {
_cacheFile = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"test.png"];
}
return _cacheFile;
}
2.5 在 // 下载剩余的[self downloadWithURL:url from:fromByte to:fromByte + netImageSize - 1]; 的下面添加方法,告诉控制器图片下载好了,可以显示在控制器的self.imageView上了,方法:代理,通知,Block,这次用Block来实现,怎么设置Block,应该看外部的控制怎么用才"优雅".
2.6 打开 viewController.m
QKFileDownload *download = [[QKFileDownload alloc]init];
[download downloadImageWithURL:[NSURL URLWithString:@"http://localhost/123icon" 此处写Block];
初步的写法:[download downloadImageWithURL:[NSURL URLWithString:@"http://localhost/123icon" completion:(){ }]; // Block写在completion前的括号里
最终写法:
[download downloadImageWithURL:[NSURL URLWithString:@"http://localhost/123icon" completion:((void)(^)(UIImage *image)){
[self.imageView setImage:image];
}];/*
注:Block的写法:
(返回值)(^Block名字)(参数类型) = ^{
// 代码写在里面
}
想法:
(void)(^)(UIImage *image) = ^{
// 代码写在这里面
}
*/
2.7.1 回到工具类.m文件,修改 - (void)downloadImageWithURL:(NSURL *)url 的方法名
改成:- (void)downloadImageWithURL:(NSURL *)url completion:((void)(^)(UIImage *image))completion{
long long netImageSize = [self netImageSizeWithURL:url];
……
}
2.7.2 回到工具类.h文件,修改方法名.回到viewController.m,再去敲Block,回车打开Block代码的方式出现了.
2.8.1 回到工具类.m文件,在
// 下载剩余的
[self downloadWithURL:url from:fromByte to:fromByte + netImageSize - 1]; 的末尾,添加 completion(self.cacheImage);
2.8.2 实现cacheImage的get方法
- (UIImage *)cacheImage
{
if (!_cacheImage) {
_cacheImage = [UIImage imageWithContentsOfFile:self.cacheFile];
}
return _cacheImage;
}
2.9 如果本地缓存已经有请求数据了,如何避免再次向服务器要数据?在long long fromByte = 0;
long long toByte = 0; 的前面添加
long long netImageSize = [self netImageSizeWithURL:url];
long long localImageSize = [self localImageSizeWithURL:self.cacheFile];
if (localImageSize == netImageSize) {
completion(self.cacheImage);
return;
}
2.10 实现 localImageSizeWithURL : self . cacheFile 方法
- (long long)localImageSizeWithURL:(NSString *)str
{
NSFileManager *mgr = [NSFileManager defaultManager];
NSDictionary *dict = [mgr attributesOfItemAtPath:str error:NULL];
return [dict[NSFileSize] longLongValue];
}
2.11 在c acheFile的get方法中,文件名被写死了,如何自动命名?方案1:随机数命名(比如arc4random),会出现的情况:下载同一个文件,每次文件名都不一样,每次都要从服务器下载
方案2:根据时间戳命名,和方案1一样的问题
方案3:MD5命名,可以保证只要url路径没变,用MD5计算的名字就不会变,这样下载同一个文件,第2次下载时,localImageSize == netImageSize条件满足,程序就会从缓存中取
为NSString添加分类
- (NSString *)MD5
{
const char *cStr = [self UTF8String];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, strlen(cStr), digest);
NSMutableString *result = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[result appendFormat:@"%02x", digest[i]];
}
return result;
}
2.12.1 打开工具类.m文件的 downloadImageWithURL:( NSURL *)url completion:((void)(^)(UIImage *image))completion 的首行位置添加代码
self.cacheFile = [url absoluteString];
2.12.2 把cacheFile的get方法改成set方法
- (void) setCacheFile:(NSString *)url
{
NSString *newFileName = [url MD5];
NSString *fullFileName = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:newFileName];
_cacheFile = fullFileName;
}
2.13 为文件下载添加多线程就是把
- (void)downloadImageWithURL:(NSURL *)url completion:(void (^)(UIImage *image))completion里的所有内容塞到
dispatch_queue_t q = dispatch_queue_create("com.ios.www", DISPATCH_QUEUE_SERIAL);
dispatch_async(q, ^{
// 塞到这里
};
然后给把Block添加到主队列里,因为Block涉及到UI界面的刷新
dispatch_async(dispatch_get_main_queue(), ^{
completion(self.cacheImage);
});