一、主體架構
整體架構類似于MVC,UIImageView需要圖片從而向控制器請求數據,圖像下載管理器承擔了控制器的責任,里面包含有全局下載隊列,操作緩存等,負責調度下載,進一步向具體下載操作請求數據。圖像下載操作負責執行真正的下載任務,之后返回下載好的圖片給控制器。
1)UIImageView實例在視圖控制器中調用方法setImageWithURLString:(NSString*)URLString來設置圖片。在該方法中首先取消掉當前正在執行的下載,否則多個下載任務(完成時間的先后不可控)會導致圖片錯亂。為了保證這個效果,需要有一個屬性來保存當前正在下載的URLString。而為了保證調用的方便,setImageWithURLString是UIImageView通過添加分類實現的,因此,需要通過runtime在運行時動態的添加一個屬性來保存當前正在下載的URLString。
2)DownloadImageManager,下載管理器是一個單例,從而保證其中的下載隊列等屬性不會被銷毀,從而建立緩存。里面有3個核心屬性,分別是1、操作緩存,以URLString為key,以下載操作為value,從而避免下載緩慢的時候發生重復請求。2、全局隊列,使用全局隊列可以控制線程的并發數。3、圖片內存緩存(沙盒緩存),通過緩存不用每次都通過網絡請求來獲取圖片,節省流量,加快速度。
3)圖像下載操作:繼承于NSOperation,通過繼承從而重寫main方法,在main方法中通過判斷cancle標志從而實現中斷下載的功能。實現取消下載是保證圖片不會錯亂的核心。
具體實現代碼:
UIImageview分類:
UIImageView+XZHWebImage.m
#define XZHCurrentURLString @"currentURLStirng"
@interface UIImageView ()
/**
* 記錄當前正在下載的URL
*/
@property (nonatomic,copy)NSString *currentURLStirng;
@end
@implementation UIImageView (XZHWebImage)
- (void)setImageWithURLString:(NSString *)URLString{
//如果對同一個cell執行了兩次請求操作,取消掉當前正在執行的
if (![URLString isEqualToString:self.currentURLStirng]&&self.currentURLStirng) {
[[XZHDownloadImageManager sharedManager] cancelDownload:self.currentURLStirng];
}
//通過下載管理器去調用相應的下載操作,通過complete 實現block回調
[[XZHDownloadImageManager sharedManager] downloadImageWithURLString:URLString complete:^(UIImage *image) {
self.image = image;
}];
//記錄正在下載的url
self.currentURLStirng = URLString;
}
/**
* 通過runtime動態添加屬性
*/
- (void)setCurrentURLStirng:(NSString *)currentURLStirng{
objc_setAssociatedObject(self, XZHCurrentURLString, currentURLStirng, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)currentURLStirng{
return objc_getAssociatedObject(self, XZHCurrentURLString);
}
@end
下載管理器
XZHDownloadImageManager.m
@interface XZHDownloadImageManager()
/**
* 操作緩存
*/
@property (nonatomic,strong)NSMutableDictionary *operationCache;
/**
* 操作隊列(下載隊列)全局隊列好處:可以控制并發數
*/
@property (nonatomic,strong)NSOperationQueue *queue;
/**
* 內存緩存,把下載好的圖片保存到內存中
*/
@property (nonatomic,strong)NSMutableDictionary *imageCache;
@end
@implementation XZHDownloadImageManager
+ (instancetype)sharedManager{
static dispatch_once_t onceToken;
//只有一個實例
static XZHDownloadImageManager *manager;
dispatch_once(&onceToken, ^{
manager = [[self alloc]init];
});
return manager;
}
/**
* 通過URLString下載并指定回調操作
*/
- (void)downloadImageWithURLString:(NSString *)URLString complete:(void(^)(UIImage *image))complete{
if ([self.operationCache objectForKey:URLString]) {
NSLog(@"正在下載");
return;
}
//開始下載前判斷有沒有緩存
if ([self checkCache:URLString]) {
UIImage *image = [self.imageCache objectForKey:URLString];
complete(image);
return;
}
//創建下載操作
//執行異步下載圖片
XZHOperation *op = [XZHOperation downloadImageOperationWithURLString:URLString downloadFinished:^(UIImage *image) {
//下載完成移除操作
[self.operationCache removeObjectForKey:URLString];
//回傳圖片
complete(image);
}];
//把操作添加到隊列中
[self.queue addOperation:op];
//把操作添加到操作緩存中
[self.operationCache setObject:op forKey:URLString];
}
/**
* 取消指定圖片的下載操作
*
*/
- (void)cancelDownload:(NSString *)URLString{
//通過url拿到操作
[self.operationCache[URLString] cancel];
//從緩存中移除
[self.operationCache removeObjectForKey:URLString];
}
- (BOOL)checkCache:(NSString *)URLStirng{
//有內存緩存,直接返回。否則從沙盒取,再保存到內存中,最后還是從內存取
//先判斷內存
if ([self.imageCache objectForKey:URLStirng]) {
NSLog(@"內存緩存");
return YES;
}
//判斷沙盒
NSString *path = [URLStirng appendCaches];
UIImage *image = [UIImage imageWithContentsOfFile:path];
if (image) {
NSLog(@"沙盒緩存");
//把圖片保存到內存緩存中
[self.imageCache setObject:image forKey:URLStirng];
return YES;
}
return NO;
}
#pragma mark -數據懶加載
- (NSOperationQueue *)queue{
if (_queue==nil) {
_queue = [[NSOperationQueue alloc]init];
}
return _queue;
}
- (NSMutableDictionary *)operationCache{
if (_operationCache==nil) {
_operationCache = [NSMutableDictionary dictionary];
}
return _operationCache;
}
- (NSMutableDictionary *)imageCache{
if (_imageCache == nil) {
_imageCache = [NSMutableDictionary dictionary];
}
return _imageCache;
}
@end
下載操作
XZHOperation.m
@interface XZHOperation()
/**
* 下載圖片的URL
*/
@property (nonatomic,copy)NSString *URLString;
/**
* 下載完成后異步回傳下載好的圖片
*/
@property (nonatomic,copy)void (^downloadFinished)(UIImage *image);
@end
@implementation XZHOperation
/**
* 封裝內部屬性,通過提供的類方法傳入所需的值賦給相應的屬性
*
*/
+ (instancetype)downloadImageOperationWithURLString:(NSString *)URLStirng downloadFinished:(void(^)(UIImage *image))downloadFinished{
XZHOperation *download = [[self alloc]init];
download.URLString = URLStirng;
download.downloadFinished = downloadFinished;
return download;
}
/**
* 通過重寫main方法來干涉操作的內部,從而實現中斷/取消下載
*/
- (void)main{
//通過包裝一個自動釋放池可以包裝整個操作內存峰值不會太高
@autoreleasepool {
//下載圖片
[NSThread sleepForTimeInterval:1];
NSURL *url = [NSURL URLWithString:self.URLString];
NSData *data = [NSData dataWithContentsOfURL:url];
//把二進制數據轉換成圖片
UIImage *image = [UIImage imageWithData:data];
//把圖片保存到沙盒中
if (data) {
[data writeToFile:[self.URLString appendCaches ] atomically:YES];
}
//在關鍵點(比較耗時的地方)盤點是否在下載期間已經取消下載了
//取消下載,直接返回
if(self.isCancelled){
NSLog(@"已經取消下載");
return;
}
//回到主線程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//判斷回調block是否已經被賦值
if (self.downloadFinished) {
self.downloadFinished(image);
}
}];
}
}
/**
* start方法一直都會被調用,無論是否被取消
*/
//- (void)start{
//
//}
@end