仿SDWebImage

仿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 清空磁盤緩存,將所有緩存目錄中的文件,全部刪除!
      實際工作,將緩存目錄直接刪除,再次創建一個同名空目錄!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章