仿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
清空磁盤緩存,將所有緩存目錄中的文件,全部刪除!
實際工作,將緩存目錄直接刪除,再次創建一個同名空目錄!
- 執行