一、主体架构
整体架构类似于MVC,UIImageView需要图片从而向控制器请求数据,图像下载管理器承担了控制器的责任,里面包含有全局下载队列,操作缓存等,负责调度下载,进一步向具体下载操作请求数据。图像下载操作负责执行真正的下载任务,之后返回下载好的图片给控制器。
screenshot.png1)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
网友评论