仿SDWebImage
目标:模拟 SDWebImage
的实现
说明:整体代码与之前博客上的演练代码的基本一致,只是编写顺序会有变化!
在模仿
SDWebImage
之前,首先需要补充一个知识点:NSOperation自定义操作
下载操作实现
#import "NSString+Path.h" @interface DownloadImageOperation() /// 要下载图像的 URL 字符串 @property (nonatomic, copy) NSString *URLString; /// 完成回调 Block @property (nonatomic, copy) void (^finishedBlock)(UIImage *image); @end @implementation DownloadImageOperation + (instancetype)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished { DownloadImageOperation *op = [[DownloadImageOperation alloc] init]; op.URLString = URLString; op.finishedBlock = finished; return op; } - (void)main { @autoreleasepool { // 利用断言要求必须传入完成回调,简化后续代码的分支 NSAssert(self.finishedBlock != nil, @"必须传入回调 Block"); // 1. NSURL NSURL *url = [NSURL URLWithString:self.URLString]; // 2. 获取二进制数据 NSData *data = [NSData dataWithContentsOfURL:url]; // 3. 保存至沙盒 if (data != nil) { [data writeToFile:self.URLString.appendCachePath atomically:YES]; } if (self.isCancelled) { NSLog(@"下载操作被取消"); return; } // 主线程回调 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.finishedBlock([UIImage imageWithData:data]); }]; } }
断言
- 断言是所有 C 语言开发者的最爱
- 断言能够在程序编码时提前预判必须满足某一个条件
- 如果条件不满足,直接让程序崩溃,从而让程序员尽早发现错误
- 断言仅在调试时有效
- 断言可以简化程序的分支逻辑
测试下载操作
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { int seed = arc4random_uniform((UInt32)self.appList.count); AppInfo *app = self.appList[seed]; // 取消之前的下载操作 if (![app.icon isEqualToString:self.currentURLString]) { // 取消之前操作 [self.operationCache[self.currentURLString] cancel]; } // 记录当前操作 self.currentURLString = app.icon; // 创建下载操作 DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:app.icon finished:^(UIImage *image) { self.iconView.image = image; // 从缓冲池删除操作 [self.operationCache removeObjectForKey:app.icon]; }]; // 将操作添加到缓冲池 [self.operationCache setObject:op forKey:app.icon]; // 将操作添加到队列 [self.downloadQueue addOperation:op]; }
框架结构设计
下载管理器
- 单例实现
+ (instancetype)sharedManager { static id instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; }
之所以设计成单例,是为了实现全局的图像下载管理
- 移植属性和懒加载代码
/// 下载队列 @property (nonatomic, strong) NSOperationQueue *downloadQueue; /// 下载操作缓存 @property (nonatomic, strong) NSMutableDictionary *operationCache; // MARK: - 懒加载 - (NSMutableDictionary *)operationCache { if (_operationCache == nil) { _operationCache = [NSMutableDictionary dictionary]; } return _operationCache; } - (NSOperationQueue *)downloadQueue { if (_downloadQueue == nil) { _downloadQueue = [[NSOperationQueue alloc] init]; } return _downloadQueue; }
- 定义方法
/// 下载指定 URL 的图像 /// /// @param URLString 图像 URL 字符串 /// @param finished 下载完成回调 - (void)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *image))finished;
- 方法实现
- (void)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished { // 检查操作缓冲池 if (self.operationCache[URLString] != nil) { NSLog(@"正在玩命下载中,稍安勿躁"); return; } // 创建下载操作 DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:URLString finished:^(UIImage *image) { // 从缓冲池删除操作 [self.operationCache removeObjectForKey:URLString]; // 执行回调 finished(image); }]; // 将操作添加到缓冲池 [self.operationCache setObject:op forKey:URLString]; // 将操作添加到队列 [self.downloadQueue addOperation:op]; }
修改 ViewController
中的代码
- 删除相关属性和懒加载方法
- 用下载管理器接管之前的下载方法
// 创建下载操作 [[DownloadImageManager sharedManager] downloadImageOperationWithURLString:self.currentURLString finished:^(UIImage *image) { self.iconView.image = image; }];
- 增加取消下载功能
/// 取消指定 URL 的下载操作 - (void)cancelDownloadWithURLString:(NSString *)URLString { // 1. 从缓冲池中取出下载操作 DownloadImageOperation *op = self.operationCache[URLString]; if (op == nil) { return; } // 2. 如果有取消 [op cancel]; // 3. 从缓冲池中删除下载操作 [self.operationCache removeObjectForKey:URLString]; }
运行测试!
缓存管理
- 定义图像缓存属性
/// 图像缓存 @property (nonatomic, strong) NSMutableDictionary *imageCache;
- 懒加载
- (NSMutableDictionary *)imageCache { if (_imageCache == nil) { _imageCache = [NSMutableDictionary dictionary]; } return _imageCache; }
- 检测图像缓存方法准备
/// 检查图像缓存 /// /// @return 是否存在图像缓存 - (BOOL)chechImageCache { return NO; }
- 方法调用
// 如果存在图像缓存,直接回调 if ([self chechImageCache]) { finished(self.imageCache[URLString]); return; }
- 缓存方法实现
- (BOOL)chechImageCache:(NSString *)URLString { // 1. 如果存在内存缓存,直接返回 if (self.imageCache[URLString]) { NSLog(@"内存缓存"); return YES; } // 2. 如果存在磁盘缓存 UIImage *image = [UIImage imageWithContentsOfFile:URLString.appendCachePath]; if (image != nil) { // 2.1 加载图像并设置内存缓存 NSLog(@"从沙盒缓存"); [self.imageCache setObject:image forKey:URLString]; // 2.2 返回 return YES; } return NO; }
运行测试
自定义 UIImageView
目标:
- 利用下载管理器获取指定
URLString
的图像,完成后设置image
- 如果之前存在未完成的下载,判断是否与给定的
URLString
一致 - 如果一致,等待下载结束
- 如果不一致,取消之前的下载操作
- 利用下载管理器获取指定
定义方法
/// 设置指定 URL 字符串的网络图像 /// /// @param URLString 网络图像 URL 字符串 - (void)setImageWithURLString:(NSString *)URLString;
- 方法实现
@interface WebImageView() /// 当前正在下载的 URL 字符串 @property (nonatomic, copy) NSString *currentURLString; @end @implementation WebImageView - (void)setImageWithURLString:(NSString *)URLString { // 取消之前的下载操作 if (![URLString isEqualToString:self.currentURLString]) { // 取消之前操作 [[DownloadImageManager sharedManager] cancelDownloadWithURLString:self.currentURLString]; } // 记录当前操作 self.currentURLString = URLString; // 创建下载操作 __weak typeof(self) weakSelf = self; [[DownloadImageManager sharedManager] downloadImageOperationWithURLString:URLString finished:^(UIImage *image) { weakSelf.image = image; }]; } @end
- 修改
ViewController
中的调用代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { int seed = arc4random_uniform((UInt32)self.appList.count); AppInfo *app = self.appList[seed]; [self.iconView setImageWithURLString:app.icon]; }
运行时机制 —— 关联对象
// MARK: - 运行时关联对象 const void *HMCurrentURLStringKey = "HMCurrentURLStringKey"; - (void)setCurrentURLString:(NSString *)currentURLString { objc_setAssociatedObject(self, HMCurrentURLStringKey, currentURLString, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)currentURLString { return objc_getAssociatedObject(self, HMCurrentURLStringKey); }
- 为了防止
Cell
重用,取消之前下载操作的同时,清空 image
self.image = nil;
SDWebImage常见面试题
1> 图片文件缓存的时间有多长:1周
_maxCacheAge = kDefaultCacheMaxCacheAge
2> SDWebImage 的内存缓存是用什么实现的?
NSCache
3> SDWebImage 的最大并发数是多少?
maxConcurrentDownloads = 6
* 是程序固定死了,可以通过属性进行调整!
4> SDWebImage 支持动图吗?GIF
#import <ImageIO/ImageIO.h> [UIImage animatedImageWithImages:images duration:duration];
5> SDWebImage是如何区分不同格式的图像的
根据图像数据第一个字节来判断的!
- PNG:压缩比没有JPG高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
- JPG:压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
- GIF:序列桢动图,特点:只支持256种颜色!最流行的时候在1998~1999,有专利的!
6> SDWebImage 缓存图片的名称是怎么确定的!
md5
- 如果单纯使用 文件名保存,重名的几率很高!
- 使用 MD5 的散列函数!对完整的 URL 进行 md5,结果是一个 32 个字符长度的字符串!
7> SDWebImage 的内存警告是如何处理的!
- 利用通知中心观察
- UIApplicationDidReceiveMemoryWarningNotification
接收到内存警告的通知- 执行
clearMemory
方法,清理内存缓存!
- 执行
- UIApplicationWillTerminateNotification
接收到应用程序将要终止通知- 执行
cleanDisk
方法,清理磁盘缓存!
- 执行
- UIApplicationDidEnterBackgroundNotification
接收到应用程序进入后台通知- 执行
backgroundCleanDisk
方法,后台清理磁盘! - 通过以上通知监听,能够保证缓存文件的大小始终在控制范围之内!
clearDisk
清空磁盘缓存,将所有缓存目录中的文件,全部删除!
实际工作,将缓存目录直接删除,再次创建一个同名空目录!
- 执行
来源:https://www.cnblogs.com/jiahao89/p/5118276.html