SDWebImage面試常問點知識點

灰太狼同志發表於2018-04-25

SDWebImage的快取策略

SDWebImage 的圖片快取預設情況採用的是 Memory 和 Disk 雙重快取機制。
下載之前先去Memory中查詢圖片資料,找到直接返回使用;
找不到再到Disk中查詢圖片資料,找到後放入Memory中再返回使用;
如果Disk中也找不到再去下載圖片;
下載到圖片後顯示圖片並將圖片資料存到Memory和Disk中。
複製程式碼

SDWebImage的Memory快取的存取和刪除機制

SDWebImage 的Memory快取使用NSCache。SDImageCache類中有一個memCache屬性。用這個屬性來進行Memory快取。

@property (strong, nonatomic) NSCache *memCache;
複製程式碼

下面程式碼用來將圖片資料存到Memory中,其中的key是圖片的url路徑。

[self.memCache setObject:image forKey:key cost:cost];
複製程式碼

下面程式碼用來獲取Memory中的圖片資料,其中的key是圖片的url路徑

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
    return [self.memCache objectForKey:key];
}
複製程式碼

SDImageCache類還監聽了UIApplicationDidReceiveMemoryWarningNotification通知,當收到記憶體警告時候清除Memory快取

[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
- (void)clearMemory {
    [self.memCache removeAllObjects];
}
複製程式碼

SDWebImage的Disk快取的存取和刪除機制

SDWebImage的Disk快取圖片在/Library/Caches/default/com.hackemist.SDWebImageCache.default資料夾下。且每個圖片的存的檔名進行了處理:圖片的檔名是圖片url路徑MD5後的字串。存取都按照這個新的檔名去操作檔案。

- (NSString *)cachedFileNameForKey:(NSString *)key {
    const char *str = [key UTF8String];
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];

    return filename;
}
複製程式碼

Disk快取有時間限制,預設是一週。

static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
複製程式碼

SDImageCache類監聽了通知來刪除Disk中過期的圖片快取。

[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(cleanDisk)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundCleanDisk)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
複製程式碼

收到通知後,都會進入- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock方法。在這個方法中將過期的Disk快取清除。

SDWebImage防止一個無效的url多次載入

SDWebImageManager有個集合屬性,這裡包含著載入失敗的url。

@property (strong, nonatomic) NSMutableSet *failedURLs;
複製程式碼

這個集合中儲存著載入失敗的url,下載之前先判斷這個集合是否包含圖片的下載url。如果在且下載的策略不是SDWebImageRetryFailed,那個直接返回下載錯誤,不再進行下載操作。 下載錯誤後判斷,加入不是一些網路錯誤,就將這個圖片url新增進failedURLs

if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
複製程式碼

SDWebImage防止同一個url多次載入

前面已經說過,SDWebImage有快取策略,再次下載是回去快取中取圖片資料,這不就可以避免同一個url多次下載了嗎?

當然不可以了。快取機制只是可以防止已經下載好的圖片再次被下載

假如我同時(幾乎同時)去下載同一個url的圖片,這是肯定還沒有快取。

[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.baidu.comimage/pic/item/1.jpg"]];
[imageView2 sd_setImageWithURL:[NSURL URLWithString:@"http://www.baidu.comimage/pic/item/1.jpg"]];
複製程式碼

SDWebImage又是如何處理的? SDWebImageDownloader中有一個可變字典屬性。

@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
複製程式碼

通過下面的程式碼我們可以看出來字典中key是圖片的url,value是一個可變陣列,陣列裡面是一個一個的字典,每個字典中儲存了下載過程回撥和完成回撥。每個字典相當於一次下載請求,但是隻有第一次的下載請求才會真正去執行下載操作。這樣就防止同一個url多次載入。

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return;
    }
    dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [NSMutableArray new];
            first = YES;
        }
        // Handle single download of simultaneous download request for the same URL
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
        NSMutableDictionary *callbacks = [NSMutableDictionary new];
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;
        //執行下載操作
        if (first) {
            createCallback();
        }
    });
}

複製程式碼

在圖片下載完成或者取消下載,根據url將URLCallbacks中的陣列移除。

http 304 Not Modified

SDWebImage也對304這種情況做了相應了處理。 假如你讀過SDWebImage原始碼,該會對這個有點了解吧!

//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.  加入返回304直接取消下載操作並從快取中返回圖片資料。
複製程式碼

相關文章