iOS三方框架之 - SDWebImage解析

對SDWebImage進行解耦

經過SDWebImageManager來進行管理,主要模塊有三個:加載模塊、緩存模塊、下載模塊。 瀏覽器

image.png

SDWebImage加載圖片的流程

先放一張加載流程圖: 緩存

image.png
UIView+WebCache.m中的sd_internalSetImageWithURL:placeholderImage:options:operationKey:setImageBlock:progress:completed 開始分析

  1. 判斷當前view有沒有任務進行,每個view只能對應一個任務,有一個全局的
typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;

用來存儲着每個view對應的下載operationbash

NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

//取消對應的Operation
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    SDOperationsDictionary *operationDictionary = [self operationDictionary];
    id operations = operationDictionary[key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}
複製代碼
  1. 開始調用loadImageWithUrl:
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            [sself sd_removeActivityIndicator];
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    //下一個runloop刷新週期對當前view進行刷新
                    [sself sd_setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
複製代碼

這一步就會根據上面那張流程圖那樣:先去查找緩存——>再去進行下載——>分兩步(UI顯示和圖片緩存),這裏UI顯示的時候會根據SDWebImageOptions來決定是否須要對圖片進行一些處理,好比:async

/**
     * 只進行內存緩存,不進行磁盤緩存
     */
    SDWebImageCacheMemoryOnly = 1 << 2,

    /**
     * 這個標誌能夠漸進式下載,顯示的圖像是逐步在下載(就像你用瀏覽器瀏覽網頁的時候那種圖片下載,一截一截的顯示
     */
    SDWebImageProgressiveDownload = 1 << 3,
    /**
     * 在加載圖片時加載佔位圖。 此標誌將延遲加載佔位符圖像,直到圖像完成加載。
     */
    SDWebImageDelayPlaceholder = 1 << 9,
    /**
     * 圖片在下載後被加載到imageView。可是在一些狀況下,咱們想要設置一下圖片(引用一個濾鏡或者加入透入動畫)
     * 使用這個來手動的設置圖片在下載圖片成功後
     */
    SDWebImageAvoidAutoSetImage = 1 << 11,
複製代碼

等等,這裏沒有列舉徹底,能夠本身看源碼枚舉裏的註釋。oop

SDWebImage緩存模塊

SDWebImage使用的是內存和磁盤雙緩存。 兩個類:SDImageCacheConfig:對當前緩存的配置SDImageCache:具體的緩存查找性能

SDImageCacheConfig中有一些參數,好比:

是否要壓縮圖片,默認YES: @property (assign, nonatomic) BOOL shouldDecompressImages; 壓縮圖片能夠提升性能,但會消耗內存動畫

是否須要內存緩存,默認YES @property (assign, nonatomic) BOOL shouldCacheImagesInMemory;ui

還有一些屬性也不一一列舉了,能夠自行查看源碼。atom

SDImageCache裏包含了兩個屬性:url

//內存緩存
@property (strong, nonatomic, nonnull) NSCache *memCache;
//磁盤緩存
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
複製代碼
  • 內存緩存 SDWebImage提供了一個繼承於NSCache的緩存類AutoPurgeCache,這個類註冊了一個內存警告的通知:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];

在收到內存警告的時候,進行緩存清理。

  • 磁盤緩存
  1. 生成一個文件目錄:
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
複製代碼
  1. SDWebImage爲每個緩存文件生成了一個md5的文件名,放到文件目錄當中。

具體讀取圖片時的源碼:

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // 檢查內存緩存
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            //檢查磁盤緩存
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                //磁盤緩存找到後緩存到內存
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}
複製代碼

SDWebImage下載模塊

一樣是兩個類SDWebImageDownloaderSDWebImageDownloaderOperation SDWebImageDownloader管理類:負責處理一些公共的信息,好比下載的相關參數、下載隊列裏的前後順序、最大下載任務數量、請求頭、參數的設置等等

//優先級枚舉:
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
    //先進先出  默認
    SDWebImageDownloaderFIFOExecutionOrder,

    //後進先出
   SDWebImageDownloaderLIFOExecutionOrder
};
//設置優先級 後進先出
  if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) 
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }
複製代碼

SDWebImageDownloaderOperation:自定義NSOperation來控制其生命週期

  1. 重寫了start方法去開始下載任務:[self.dataTask resume];
  2. 下載完成後的回調處理
相關文章
相關標籤/搜索