SDWebImage面试常问点知识点

2,594 阅读4分钟

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直接取消下载操作并从缓存中返回图片数据。