聊聊怎么阅读SDWebImage源码

关于SDWebImage的介绍就不多说了,网上很多。我们主要聊聊怎么阅读SDWebImage的源码。 阅读源码前首先我们要思考几个问题: 1.SDWebImage的下载流程是怎么样的? 2.SDWebImage怎么处理多线程并发? 3.SDWebImage怎么处理缓存? 4.SDWebImage怎么设置图片? 5.缓存多久?

1.SDWebImage的下载流程是怎么样的? 下载流程分两个部分来讲,首先来说缓存查询流程,如图:

查询缓存.jpg

1.首先查内存缓存 主要代码

// First check the in-memory cache...
  UIImage *image = [self imageFromMemoryCacheForKey:key];
  BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
  if (shouldQueryMemoryOnly) {
      if (doneBlock) {
          doneBlock(image, nil, SDImageCacheTypeMemory);
      }
      return nil;
  }

2.没有内存缓存,异步访问磁盘缓存。

if (options & SDImageCacheQueryDiskSync) {
        queryDiskBlock();
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
void(^queryDiskBlock)(void) =  ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        
        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            SDImageCacheType cacheType = SDImageCacheTypeDisk;
            if (image) {
                // the image is from in-memory cache
                diskImage = image;
                cacheType = SDImageCacheTypeMemory;
            } else if (diskData) {
                // decode image data only if in-memory cache missed
                diskImage = [self diskImageForKey:key data:diskData];
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = SDCacheCostForImage(diskImage);
                    [self.memCache setObject:diskImage forKey:key cost:cost];
                }
            }
            
            if (doneBlock) {
                if (options & SDImageCacheQueryDiskSync) {
                    doneBlock(diskImage, diskData, cacheType);
                } else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, cacheType);
                    });
                }
            }
        }
    };

3.都没有查询到就开始下载

接下来说说下载流程

下载流程.jpg

核心代码

LOCK(self.operationsLock);
    SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
    if (!operation) {
        operation = createCallback();
        __weak typeof(self) wself = self;
        operation.completionBlock = ^{
            __strong typeof(wself) sself = wself;
            if (!sself) {
                return;
            }
            LOCK(sself.operationsLock);
            [sself.URLOperations removeObjectForKey:url];
            UNLOCK(sself.operationsLock);
        };
        [self.URLOperations setObject:operation forKey:url];
        // Add operation to operation queue only after all configuration done according to Apple's doc.
        // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
        [self.downloadQueue addOperation:operation];
    }
    UNLOCK(self.operationsLock);

    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
    
    SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
    token.downloadOperation = operation;
    token.url = url;
    token.downloadOperationCancelToken = downloadOperationCancelToken;

以上代码的作用 1.根据Url取缓存DownloaderOperation

SDWebImageDownloaderOperation *
operation = [self.URLOperations objectForKey:url];

2.如果没有缓存DownloaderOperation,新建一个DownloaderOperation缓存

[self.URLOperations setObject:operation forKey:url];

3.operation添加到下载队列

 [self.downloadQueue addOperation:operation];

4.如果DownloaderOperation已经在队列了则返回

id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
    
    SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
    token.downloadOperation = operation;
    token.url = url;
    token.downloadOperationCancelToken = downloadOperationCancelToken;

    return token;

5.如果已经下载完成,删除DownloaderOperation

operation.completionBlock = ^{
            __strong typeof(wself) sself = wself;
            if (!sself) {
                return;
            }
            LOCK(sself.operationsLock);
            [sself.URLOperations removeObjectForKey:url];
            UNLOCK(sself.operationsLock);
        };

特别说明一点就是LOCK,UNLOCK 就是为了处理多线程并发的

#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);

6.缓存图片主要代码

if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }

实现缓存的主要代码

- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // 1.如果内存缓存启用,先存入内存缓存。
    if (self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    
    if (toDisk) {
        // 2.在 ioQueue 中串行处理所有磁盘缓存
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
                    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();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    
    // 3.创建放缓存文件的文件夹
    if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
        [self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    // 4.根据 image 的 远程url 生成本地缓存图片对应的 url先将远程的 url 进行 md5加密,作为文件名,然后拼接到默认的缓存路径下,作为缓存文件的 url
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    // 5.将imageData存入磁盘
    [imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
    
    // 6. 防止 icloud 备份缓存
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

- (nullable NSString *)cachedFileNameForKey:(nullable 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);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    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], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
    return filename;
}

最后就是如何设置图片了,主要代码如下

dispatch_main_async_safe(^{
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
                    callCompletedBlockClojure();
                });

缓存时间

// 默认缓存1 周
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; 

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOS122-移动混合开发研究院

ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!

简介 项目主页: ReactiveCocoa 实例下载: https://github.com/ios122/ios122 简评: 最受欢迎,最有价值的iOS响...

27980
来自专栏landv

C# 实现登录并跳转界面

62030
来自专栏Guangdong Qi

调用 MFMessageComposeViewController UINavigationBar 高出一截

17740
来自专栏技术之路

ios 接收 c# socket udp 组播

最近用wcf 服务 给ios和安卓做接口,做了几个ios的项目  用udp 组播 让ios多终端接收和刷新方法 做一个简单的小例子会把工程给大家下载的   c#...

27580
来自专栏陈满iOS

[iOS源码笔记]·第三方网络图片处理框架:SDWebImage网络下载及缓存管理策略

typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _N...

15410
来自专栏雨尘分享

ReactiveCocoa 入门知识——归总篇

23740
来自专栏coding...

iOS 使用 socket 即时通信(非第三方库)效果模型图分析UI方面代码部分Demo地址

因为大家学C 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底层的通信。

11440
来自专栏iOS开发攻城狮的集散地

UIActivityViewController系统原生分享-仿简书分享

46680
来自专栏转载gongluck的CSDN博客

第14章 高级I/O函数

设置套接字时间限制: 1、使用alarm函数和SIGALRM信号 2、使用由select提供的时间限制 3、使用SO_RCVTIMEO和SO_SNDTIM...

30040
来自专栏向治洪

RCTEventEmitter使用

在0.27版本之前,RN的Native端向js端发射消息主要通过sendDeviceEventWithName的方式,相关代码如下。 @synthesize b...

48570

扫码关注云+社区

领取腾讯云代金券