模仿SDWebImage的实现思路及代码

一、主体架构

整体架构类似于MVC,UIImageView需要图片从而向控制器请求数据,图像下载管理器承担了控制器的责任,里面包含有全局下载队列,操作缓存等,负责调度下载,进一步向具体下载操作请求数据。图像下载操作负责执行真正的下载任务,之后返回下载好的图片给控制器。

screenshot.png

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

你可能感兴趣的:(模仿SDWebImage的实现思路及代码)