一步一步拆解SDWebImage SDK。
今天只涉及两个主要的类:
- BJCAImageCache
- BJCAWebImageView
//BJCAImageCache.h文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface BJCAImageCache : NSObject
{
NSMutableDictionary *cache;
NSString *diskCachePath;
}
+(BJCAImageCache *)sharedImageCache;
//存储图片
-(void)storeImage:(UIImage *)image forKey:(NSString *)key;
-(void)storeImage:(UIImage *)image foeKey:(NSString *)key toDisk:(BOOL)toDisk;
//获取图片的方法
-(UIImage *)imageForKey:(NSString *)key;
-(UIImage *)imageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
//删除图片的方法
-(void)removeImageFrokey:(NSString *)key;
-(void)clearMemory;
-(void)clearDisk;
-(void)cleanDisk;
@end
NS_ASSUME_NONNULL_END
//BJCAImageCache.m文件
#import "BJCAImageCache.h"
/**
常用的摘要算法,比如MD5,SHA1等。
摘要算法就是,一种能产生特殊输出格式的算法,无论内容长度是多少,最后输出的都是同样的长度。
*/
#import <CommonCrypto/CommonDigest.h>
/**
定义一个星期的时间;
用static定义局部变量为静态变量,在函数调用结束之后不释放继续保留值。
*/
static NSInteger kMaxCacheAge = 60 * 60 * 24 * 7;
static BJCAImageCache *instance;
@implementation BJCAImageCache
#pragma mark NSObject
-(instancetype)init
{
if (self = [super init])
{
cache = [[NSMutableDictionary alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
//应用终止的通知,在应用在前台,双击home键,终止应用的时候会调用,单击home键回到桌面不会调用
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(willTerminate)
name:UIApplicationWillTerminateNotification
object:nil];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
diskCachePath = [paths[0] stringByAppendingPathComponent:@"ImageCache"];
if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath])
{
//创建目录
[[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil];
}
}
return self;
}
-(void)dealloc
{
//不要写超类的方法
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
}
/**
程序将要终止的时候,将磁盘清空
*/
-(void)willTerminate
{
[self cleanDisk];
}
/**
收到了内存警告*/
-(void)didReciveMemoryWaring
{
[self clearMemory];
}
#pragma mark ImageCache (private)
-(NSString *)cachePathForKey:(NSString *)key
{
//const char 是表示常量型的字符
const char *str = [key UTF8String];
//表示无符号的字符类型
unsigned char r[CC_MD5_DIGEST_LENGTH];
//MD5加密
CC_MD5(str, strlen(str), r);
//x表示以十六进制形式输出,02表示不足两位前面补0,超过两位不影响。
NSString *fileName = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8],r[9],r[10],r[11],r[12],r[13],r[14],r[15]];
return [diskCachePath stringByAppendingPathComponent:fileName];
}
#pragma mark ImageCache
+(BJCAImageCache *)sharedImageCache
{
if (instance == nil)
{
instance = [[BJCAImageCache alloc] init];
}
return instance;
}
-(void)storeImage:(UIImage *)image forKey:(NSString *)key
{
[self storeImage:image foeKey:key toDisk:YES];
}
-(void)storeImage:(UIImage *)image foeKey:(NSString *)key toDisk:(BOOL)toDisk
{
if (image == nil)
{
return;
}
[cache setObject:image forKey:key];
if (toDisk)
{
/**
iOS中有两种转化图片的简单方法:
1、UIImageJPEGRepresentation 图片、压缩系数。
压缩后图片较小,图片质量也无较大差异,日常中主要用这个方法
2、UIImagePNGRepresentation 图片
压缩图片的图片较大
*/
[[NSFileManager defaultManager] createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, 1.0) attributes:nil];
}
}
-(UIImage *)imageForKey:(NSString *)key
{
return [self imageForKey:key fromDisk:YES];
}
-(UIImage *)imageForKey:(NSString *)key fromDisk:(BOOL)fromDisk
{
UIImage *image = [cache objectForKey:key];
if (!image && fromDisk)
{
image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:[self cachePathForKey:key]]];
if (image != nil)
{
[cache setObject:image forKey:key];
}
}
return image;
}
-(void)removeImageFrokey:(NSString *)key
{
[cache removeObjectForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil];
}
-(void)clearMemory
{
[cache removeAllObjects];
}
-(void)clearDisk
{
[[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil];
[[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil];
}
-(void)cleanDisk
{
//从现在开始的-7天
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-kMaxCacheAge];
NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:diskCachePath];
//可以枚举指定目录中的每个文件
for (NSString *fileName in fileEnumerator)
{
NSString *filePath = [diskCachePath stringByAppendingFormat:fileName];
//获取文件的大小。创建时间等属性。
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
/**
获取文件的修改时间;
获取两个时间中较晚的那个时间;
*/
if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate])
{
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
}
}
@end
//BJCAWebImageView.h文件
#import <UIKit/UIKit.h>
@class BJCAWebImageDownloadOperation;
NS_ASSUME_NONNULL_BEGIN
@interface BJCAWebImageView : UIImageView
{
UIImage *placeHolderImage;
BJCAWebImageDownloadOperation *currentOperation;
}
-(void)setImageWithURL:(NSURL *)url;
-(void)downloadFinishedWithImage:(UIImage *)image;
@end
@interface BJCAWebImageDownloadOperation : NSOperation
{
NSURL *url;
BJCAWebImageView *delegate;
}
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, weak) BJCAWebImageView *delegate;
-(id)initWithURL:(NSURL *)url delegate:(BJCAWebImageView *)delegate;
@end
NS_ASSUME_NONNULL_END
//BJCAWebImageView.m文件
#import "BJCAWebImageView.h"
#import "BJCAImageCache.h"
static NSOperationQueue *downloadQueue;
static NSOperationQueue *cacheInQueue;
@implementation BJCAWebImageView
#pragma mark RemoteImageView
-(void)setImageWithURL:(NSURL *)url
{
if (currentOperation != nil)
{
[currentOperation cancel];//从队列中删除
currentOperation = nil;
}
//保存占位图图像,以便在视图被重用的时候重新应用占位图
if (placeHolderImage == nil)
{
placeHolderImage = self.image;
}
else
{
self.image = placeHolderImage;
}
//完整的url字符串当做key
UIImage *cachedImage = [[BJCAImageCache sharedImageCache] imageFromKey:[url absoluteString]];
if (cachedImage)
{
self.image = cachedImage;
}
else
{
if (downloadQueue == nil)
{
downloadQueue = [[NSOperationQueue alloc] init];
[downloadQueue setMaxConcurrentOperationCount:8];
}
currentOperation = [[BJCAWebImageDownloadOperation alloc] initWithURL:url delegate:self];
[downloadQueue addOperation:currentOperation];
}
}
-(void)downloadFinishedWithImage:(UIImage *)image
{
self.image = image;
currentOperation = nil;
}
@end
@implementation BJCAWebImageDownloadOperation
@synthesize url, delegate;
-(id)initWithURL:(NSURL *)url delegate:(BJCAWebImageView *)delegate
{
if (self = [super init])
{
self.url = url;
self.delegate = delegate;
}
return self;
}
/**
NSOperation有两个方法,main()和start()。如果想使用同步,就把逻辑写在main方法中,
如果想使用异步,就写在start方法中。
*/
-(void)main
{
if (self.isCancelled)
{
return;
}
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];
if (!self.isCancelled)
{
[delegate performSelectorOnMainThread:@selector(downloadFinishedWithImage:) withObject:image waitUntilDone:YES];
}
if (cacheInQueue == nil)
{
cacheInQueue = [[NSOperationQueue alloc] init];
[cacheInQueue setMaxConcurrentOperationCount:2];
}
NSString *cacheKey = [url absoluteString];
BJCAImageCache *imageCache = [BJCAImageCache sharedImageCache];
//现在将图片写入缓存中,不需要等待缓存写入操作队列完成
[imageCache storeImage:image foeKey:cacheKey toDisk:NO];
//将下一个缓存操作设置成命令对象,以避免影响下一个下载错误
NSInvocation *cacheInINvocation = [NSInvocation invocationWithMethodSignature:[[imageCache class] instanceMethodSignatureForSelector:@selector(storeImage:forKey:)]];
[cacheInINvocation setTarget:imageCache];
[cacheInINvocation setSelector:@selector(storeImage:forKey:)];
[cacheInINvocation setArgument:&image atIndex:2];
[cacheInINvocation setArgument:&cacheKey atIndex:3];
[cacheInINvocation retainArguments];
NSInvocationOperation *cacheInOperation = [[NSInvocationOperation alloc] initWithInvocation:cacheInINvocation];
[cacheInQueue addOperation:cacheInOperation];
}
@end