美文网首页
SDWebImage源码阅读

SDWebImage源码阅读

作者: 廖小凡 | 来源:发表于2020-05-14 16:31 被阅读0次

    序言

    SDWebImage作为GitHub上拥有22.2k星星的开源库,是值得我们去阅读它的源码,很多可以学习的东西,一个简单的API:sd_setImageWithURL,其中就包括了查找缓存数据,下载图片数据,缓存数据,存进磁盘等操作,让我们来看一下是如何实现的吧。

    内容

    SDWebImageManager

    SDWebImageManager作为一个管理类,提供接口给外部调用,自己在类里实现了一个三级缓存机制。

    • 下载数据,SDWebImageDownloader实现的。
    • 内存缓存,SDImageCache实现的。
    • 磁盘缓存,SDImageCache实现的。
      关键代码
    - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(nullable SDInternalCompletionBlock)completedBlock {
        __weak SDWebImageCombinedOperation *weakOperation = operation;
        //查询缓存池里有没有这个数据
        operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
            NSString *key = [self cacheKeyForURL:url];
            BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
                && (!cachedImage || options & SDWebImageRefreshCached)
                && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
            if (shouldDownload) {
                __weak typeof(strongOperation) weakSubOperation = strongOperation;
                //根据url下载数据
                strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                    //将数据存进缓存池
                    NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
                    [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
            }
            else {
                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
        }
    }
    

    这里只截了一些关键代码出来,具体SDWebImage针对很多情况都有一些处理,有兴趣的同学可以去深入了解下。

    SDWebImageDownloader

    downloadImageWithURL方法是SDWebImageDownloader的,这个类是负责下载数据的。
    关键代码

    - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                       options:(SDWebImageDownloaderOptions)options
                                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                     completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
        //将request传给SDWebImageDownloaderOperation
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        operation.shouldDecompressImages = sself.shouldDecompressImages;
    }
    

    SDWebImageDownloader里面用了SDWebImageDownloaderOperation这个类来下载数据,查看这个类能发现是用的NSURLSession来实现下载数据的。

    SDImageCache

    queryCacheOperationForKeystoreImage都是SDImageCache里面的方法,这个类是负责缓存数据。
    关键代码

    - (void)storeImage:(nullable UIImage *)image
             imageData:(nullable NSData *)imageData
                forKey:(nullable NSString *)key
                toDisk:(BOOL)toDisk
            completion:(nullable SDWebImageNoParamsBlock)completionBlock {
        if (self.config.shouldCacheImagesInMemory) {
            NSUInteger cost = SDCacheCostForImage(image);
            [self.memCache setObject:image forKey:key cost:cost];
        }
        if (toDisk) {
            dispatch_async(self.ioQueue, ^{
                @autoreleasepool {
                    NSData *data = imageData;
                    if (!data && image) {
                        SDImageFormat format;
                        if (SDCGImageRefContainsAlpha(image.CGImage)) {
                            format = SDImageFormatPNG;
                        } else {
                            format = SDImageFormatJPEG;
                        }
                        data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
                    }
                    [self _storeImageDataToDisk:data forKey:key];
                }
                
                if (completionBlock) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completionBlock();
                    });
                }
            });
        }
    }
    
    - (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
        if (!imageData || !key) {
            return;
        }
        
        if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
            [self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
        }
        NSString *cachePathForKey = [self defaultCachePathForKey:key];
        NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
        [imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
        if (self.config.shouldDisableiCloud) {
            [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
        }
    }
    

    从代码里可以看出,内存缓存是用了SDMemoryCache这个类,继承于NSCache,而磁盘缓存是用writeToURL写进文件里面。

    总结

    • 为什么要用NSCache?
      NSCache是系统创建用来缓存的对象,有做一些专门的处理,比如,系统资源耗尽,会自动删减缓存,会删减最久未使用的对象,并且是线程安全的。
    • 为什么得创建那么多类?
      分工明确,哪个对象负责哪部分业务,符合面向对象设计的单一职责原则。
    • 缓存机制是怎样的?
      流程大概是这样的,SDWebImage拿到url,会先从内存缓存里面找,找到直接返回,找不到就去磁盘缓存里面找,找到返回并添加到内存缓存里面,找不到才去下载,下载后存进磁盘缓存和磁盘缓存。
    • 读写磁盘数据如何避免耗时操作阻塞线程?
      耗时操作交给子线程执行。
    • 如何保证线程安全?
      SDWebImage里面是用的GCD的信号量加锁。

    相关文章

      网友评论

          本文标题:SDWebImage源码阅读

          本文链接:https://www.haomeiwen.com/subject/pkeunhtx.html