在开发过程中很多人都会使用SDWebImage来进行网络图片的缓存,说实话,这个第三方也确实好用,但依照本人的性格,还是一直在想做一版自己的本地缓存,比如下载的图片存到本地,下次再有相同的图片需要加载,就不用再花冤枉流量来下载了,这次的尝试虽然相比SDWebImage会差很远,但是这只是一个开始,毕竟本人的项目经验有限,但会在以后的学习中不断的对这个代码进行优化,毕竟自己的代码可控性要比第三方大得多。
最新代码的GitHub:https://github.com/YRunIntoLove/YWebImage
基本的思路就是:第一次加载图片是要下载的,在下载完成后,存到沙盒目录一个固定的文件夹(YWebImageFile)下,下次再有相同的url图片的时候,首先会从这个文件夹中进行查找,如果存在,从文件中取出,如果不存在,那就下载,待下载完毕后存到该文件夹下,因为这个类是想用在公司的项目中,用的语言就不再是Swfit
从网上查找高清大图,因为这样子才会观察得到一个过程,百度就好,大家都懂得,这里是楼主测试用的图片地址,下面测试的url是http://c.hiphotos.baidu.com/zhidao/pic/item/730e0cf3d7ca7bcb48f80cb9bc096b63f724a8a1.jpg,测试url的第三个。
static NSString * testImageURL1 = @"http://www.bz55.com/uploads/allimg/150417/139-15041G02614.jpg";
static NSString * testImageURL2 = @"http://a.hiphotos.baidu.com/zhidao/pic/item/faedab64034f78f0b7111ba67b310a55b3191c48.jpg";
static NSString * testImageURL3 = @"http://c.hiphotos.baidu.com/zhidao/pic/item/730e0cf3d7ca7bcb48f80cb9bc096b63f724a8a1.jpg";
- (IBAction)startLoadImage:(id)sender
{
//默认初始化label为本地
self.label.text = @"本地图片!";
NSString * imageURL = self.inputView.text;
//可看进度的方法
[self.imageView yw_setImageWithUrl:imageURL withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {
//更改Label
NSString * progress = [NSString stringWithFormat:@"%.1f%%",(didFinishTotal * 1.0 / Total) * 100.0];
self.label.text = progress;
}];
}
不知道为啥,今天的网速格外的好,从打印的效果来看表示本地已经存在了,不信还可以看看此文件下已经有了缓存文件,如果加载出现了App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.的打印提示,可以参考之前的一篇博客App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure 解决
文件中的缓存:
效果图完毕,来说说原理吧,其实原理前面也说了,就是首先根据url来判断本地是否已经存在UIImage对象,如果存在,直接取出,如果不存在,那么就需要下载,然后再将UIImage取出,设置给UIImageView即可,说到文件的操作,用到的一个系统单例是:NSFileManager,以及网络下载NSURLSession,不得不说楼主是第一次接触这两个类,惭愧,但这两个类的功能也是非常的强,特别是NSURLSession,是用来替代之前的NSURLConnection的,功能很爽,楼主最近也一直在接触这个类。不说这个了,上代码来瞅瞅吧,楼主的习惯就是分成几个类,所以按照类来说明吧。
这个类的功能就是:如果本地有这个图片文件,从本地中获取图片,是为了在后面的类目中进行调用的,外界用的时候不需要调用的,在以后的维护中会陆续添加一些新的功能,一下是这个类的声明文件:
//
// YWebFileManager.h
// WebImageDemo
//
// Created by YueWen on 16/3/20.
// Copyright © 2016年 YueWen. All rights reserved.
//
#import
#import
/**
* 负责处理本地文件(沙盒存储)的管理者
*/
NS_CLASS_AVAILABLE_IOS(7_0) @interface YWebFileManager : NSObject
/**
* 默认存储文件夹的大小,单位为MB
*/
//@property (nonatomic, copy, readonly)NSString * fileSize;
/**
* 单例方法
*
* @return YWebFileManager单例对象
*/
+ (instancetype)shareInstanceType NS_AVAILABLE_IOS(7_0);
/**
* 在沙盒中目录中默认存储的文件夹中是否存在该文件
*
* @param url 图片存在的url
*
* @return true表示存在,false表示不存在
*/
- (BOOL)fileIsExist:(NSString *)url NS_AVAILABLE_IOS(7_0);
/**
* 根据url获取存在本地的图片
*
* @param url 下载的url
*
* @return 存在的返回UIImage,不存在返回nil
*/
- (UIImage *)imageWithURL:(NSString *)url NS_AVAILABLE_IOS(7_0);
@end
这里面有两个属性,重写一下这两个属性的getter方法方便取值,一个是获取系统单例NSFileManager,另一个就是获取沙盒路径
#pragma mark - Getter
-(NSFileManager *)fileManager
{
return [NSFileManager defaultManager];
}
-(NSString *)documentPath
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) firstObject];
}
//文件夹是否在沙盒存在
- (BOOL)folderIsExist:(NSString *)folderPath
{
return [self.fileManager fileExistsAtPath:folderPath];
}
//沙盒目录中默认存储文件夹中是否存在这个文件
- (BOOL)fileIsExist:(NSString *)url
{
//拼接路径
NSString * path = [self.documentPath stringByAppendingFormat:@"/%@/%@",defaultFolderName,url];
return [self.fileManager fileExistsAtPath:path];
}
//根据保存的路径获取图片对象
-(UIImage *)imageWithURL:(NSString *)url
{
//不存在图片返回nil
if (![self fileIsExist:url])
{
return nil;
}
//拼接路径
NSString * path = [self.documentPath stringByAppendingFormat:@"/%@/%@",defaultFolderName,url];
//存在图片返回图片
return [UIImage imageWithContentsOfFile:path];
}
//
// YWebDownManager.h
// WebImageDemo
//
// Created by YueWen on 16/3/20.
// Copyright © 2016年 YueWen. All rights reserved.
//
#import
#import
/**
* 下载成功后的回调
*
* @param path 下载成功在本地的路径
*/
typedef void(^DownManagerFinishBlock)(NSString * path);
/**
* 下载过程中的回调
*
* @param didFinish 本次下载的文件大小
* @param didFinishTotal 至此一共下载文件的大小
* @param Total 一共需要下载文件的大小
*/
typedef void(^DownManagerProgressBlock)(CGFloat didFinish,CGFloat didFinishTotal,CGFloat Total);
NS_CLASS_AVAILABLE_IOS(7_0) @interface YWebDownManager : NSObject
//开始下载图片
- (void)startDownImagePath:(NSString *)imagePath NS_AVAILABLE_IOS(7_0);
- (void)startDownImageURL:(NSURL *)imageURL NS_AVAILABLE_IOS(7_0);
//设置相关回调
- (void)downManagerFinishBlockHandle:(DownManagerFinishBlock)downManagerFinishBlockHandle;
- (void)downManagerProgressBlockHandle:(DownManagerProgressBlock)downManagerProgressBlockHandle;
@end
@interface YWebDownManager ()
@property (nonatomic, copy)NSString * imagePath; //记录图片url的字符串path
@property (nonatomic, copy)NSURL * imageURL; //请求图片的url
@property (nonatomic, copy)NSString * imageName; //转型后的图片名称
@property (nonatomic, copy)NSString * documentPath; //沙盒路径
@property (nonatomic, copy)DownManagerFinishBlock finishBlockHandle; //下载完成后的回调
@property (nonatomic, copy)DownManagerProgressBlock progressBlockHandle; //下载过程中的回调
@end
#pragma mark - 设置回调的方法
-(void)downManagerFinishBlockHandle:(DownManagerFinishBlock)downManagerFinishBlockHandle
{
self.finishBlockHandle = downManagerFinishBlockHandle;
}
-(void)downManagerProgressBlockHandle:(DownManagerProgressBlock)downManagerProgressBlockHandle
{
self.progressBlockHandle = downManagerProgressBlockHandle;
}
#pragma mark - Document Path
- (NSString *)documentPath
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) firstObject];
}
#pragma mark - Image Name Base64
-(NSString *)imageName
{
return [YWebDataHandle imageNameForBase64Handle:_imageURL.absoluteString];
}
#pragma mark - 开始下载图片的方法
-(void)startDownImagePath:(NSString *)imagePath
{
NSLog(@"开始下载图片啦,路径为:%@",imagePath);
//赋值
_imagePath = imagePath;
//创建url对象
NSURL * downURL = [[NSURL alloc]initWithString:_imagePath];
//开始根据URL请求图片
[self startDownImageURL:downURL];
}
- (void)startDownImageURL:(NSURL *)imageURL
{
//开始赋值
_imageURL = imageURL;
//创建请求对象
NSURLRequest * request = [NSURLRequest requestWithURL:_imageURL];
//创建网络请求对象
NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
//获取下载对象
NSURLSessionDownloadTask * downLoadTask = [session downloadTaskWithRequest:request];
//开始请求
[downLoadTask resume];
}
#pragma mark - NSURLSessionDownload Delegate
//下载完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
//路径字符串
NSString * path = [NSString stringWithFormat:@"%@/YWebImageFile/%@",self.documentPath,self.imageName];
//获取创建下载到的路径url
NSURL * url = [NSURL fileURLWithPath:path];
//获取文件管理者
NSFileManager * fileManager = [NSFileManager defaultManager];
//存到文件
[fileManager moveItemAtURL:location toURL:url error:nil];
//主线程回调
dispatch_async(dispatch_get_main_queue(), ^{
//执行回调,传出路径
if (self.finishBlockHandle) {
self.finishBlockHandle(path);
}
});
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//主线程进行回调
dispatch_async(dispatch_get_main_queue(), ^{
//执行过程的回调
if (self.progressBlockHandle) {
self.progressBlockHandle(bytesWritten,totalBytesWritten,totalBytesExpectedToWrite);
}
});
}
NS_AVAILABLE_IOS(7_0) @interface YWebDataHandle : NSObject
/**
* 将路径或者url转成base64处理的字符串
*
* @param path 需要处理的字符串
*
* @return 处理完毕的字符串
*/
+ (NSString *)imageNameForBase64Handle:(NSString *)path;
@end
@implementation YWebDataHandle
+(NSString *)imageNameForBase64Handle:(NSString *)path
{
NSData * data = [path dataUsingEncoding:NSUTF8StringEncoding];
NSString * imageNameBase = [data base64EncodedStringWithOptions:0];
return [imageNameBase substringToIndex:imageNameBase.length - 2];
}
@end
#import
/**
* 下载过程中的回调
*
* @param didFinish 本次下载的文件大小
* @param didFinishTotal 至此一共下载文件的大小
* @param Total 一共需要下载文件的大小
*/
typedef void(^DownManagerProgressBlock)(CGFloat didFinish,CGFloat didFinishTotal,CGFloat Total);
@interface UIImageView (YWebImage)
//根据url设置图片
- (void)yw_setImageWithUrl:(NSString *)url;
- (void)yw_setImageWithUrl:(NSString *)url
withProgressHandle:(DownManagerProgressBlock)progresshandle;
//根据url设置图片,并支持默认占位图
- (void)yw_setImageWithUrl:(NSString *)url
placeHolderImage:(UIImage *)placeHodlerImage;
- (void)yw_setImageWithUrl:(NSString *)url
placeHolderImage:(UIImage *)placeHodlerImage
withProgressHandle:(DownManagerProgressBlock)progresshandle;
@end
-(void)yw_setImageWithUrl:(NSString *)url
withProgressHandle:(DownManagerProgressBlock)progresshandle
{
//处理url
NSString * urlHandle = [YWebDataHandle imageNameForBase64Handle:url];
//本地查询
if([[YWebFileManager shareInstanceType] fileIsExist:urlHandle])//如果本地存在返回图片
{
NSLog(@"本地已经存在这个图片了!");
self.image = [[YWebFileManager shareInstanceType] imageWithURL:urlHandle];
}
//不存在需要根据url下载
else
{
NSLog(@"本地没有这个图片!");
//开始下载
[self downImage:url withProgressHandle:progresshandle];
}
}
{
[self yw_setImageWithUrl:url withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {}];
}
- (void)yw_setImageWithUrl:(NSString *)url placeHolderImage:(UIImage *)placeHodlerImage
{
[self yw_setImageWithUrl:url placeHolderImage:placeHodlerImage withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {}];
}
- (void)yw_setImageWithUrl:(NSString *)url placeHolderImage:(UIImage *)placeHodlerImage withProgressHandle:(DownManagerProgressBlock)progresshandle
{
//设置占位图
self.image = placeHodlerImage;
//开始设置图片
[self yw_setImageWithUrl:url withProgressHandle:progresshandle];
}
//开始下载图片
- (void)downImage:(NSString *)url
{
[self downImage:url withProgressHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {}];
}
//开始下载图片
- (void)downImage:(NSString *)url
withProgressHandle:(DownManagerProgressBlock)progresshandle
{
YWebDownManager * webDownManager = [[YWebDownManager alloc]init];
//开始下载
[webDownManager startDownImagePath:url];
//设置下载完毕的回调
[webDownManager downManagerFinishBlockHandle:^(NSString *path) {
//获得当前的图片对象
UIImage * image = [UIImage imageWithContentsOfFile:path];
self.image = image;
}];
//设置下载过程的回调
[webDownManager downManagerProgressBlockHandle:^(CGFloat didFinish, CGFloat didFinishTotal, CGFloat Total) {
//进行回调
progresshandle(didFinish,didFinishTotal,Total);
}];
}