iOS------SDWebImage源码

一,简介

一个异步图片下载及缓存的库

特性:

  • 一个扩展UIImageView分类的库,支持加载网络图片并缓存图片
  • 异步图片下载器
  • 异步图片缓存和自动图片有效期限管理
  • 支持GIF动态图片
  • 支持WebP
  • 背景图片减压
  • 保证同一个URL不会再次下载
  • 保证无效的URL不会重新加载
  • 保证主线程不会死锁
  • 性能优越
  • 使用GCD和ARC
  • 支持ARM64位处理器

二,原理

只要有图片的url,就能下载图片,使用SDWebImage的好处就是缓存机制,每次取图片先判断是否在内存中,再到缓存中查找 ,找到了直接加载,在缓存找不到才重新下载。url也会被记录,是否是失效的url,是则不会再尝试。下载的图片会缓存,用于下次可以直接加载。图片的下载,解码,转码都异步进行,不会阻塞主线程。

概念:

图片的缓存
指将已经下载的图片保存在内存或磁盘中,以便在后续的加载请求中快速获取,避免重复下载和提高加载速度。可以减轻网络负担并改善用户体验。在 iOS 开发中,常用的图片缓存方案是将图片存储在内存缓存和磁盘缓存中。
图片下载
图片下载是指从网络获取图片数据的过程。下载图片的步骤包括创建请求、发送请求、接收响应和处理响应数据。
图片解码
图片解码是将下载的图片数据解析为可供应用程序使用的图像格式(像素数据)的过程。常见的图像格式包括 JPEG、PNG、GIF、WebP 等。
图片转码
将图像从一种格式转换为另一种格式的过程。在某些情况下,您可能需要将下载或解码后的图像进行转码,以便与特定的需求或平台兼容。

三,SDWebImage组织架构

在这里插入图片描述

类的作用

  • SDImageCache

负责图片的缓存,设置缓存的类型,方式,路径等

  • SDWebImageCompat

兼容类,定义了很多宏和一个转换图片的方法

  • SDWebImageDecoder

解码器,让图片色彩转换(涉及到color space)

  • SDWebImageDownloader

负责图片的下载队列,下载器,设置下载相关,要用到SDWebImageDownloaderOperation,

  • SDWebImageDownloaderOperation

负责正真的图片下载请求,下载器的操作,

  • SDWebImageManager

管理图片下载,取消操作,判断url是否已缓存等,是总的管理类,维护了SDWebImageDownloader实例和一个SDImageCache实例,是下载和缓存的桥梁。

  • SDWebImageOperation

图片操作,后面很多类都要用到

  • SDWebImagePrefetcher

预抓取器,预先下载urls中的图片

  • UIButton+WebCache

按钮图片的缓存

  • UIImage+GIF

缓存gif

  • NSData+ImageContentType

判断图片的类型,png/jpeg/gif/webp

  • UIImage+MultiFormat

缓存多种格式的图片,要用到NSData+ImageContentType的判断图片类型方法和UIImage+GIF的判断是否为gif图片方法,以及ImageIO里面的方法

  • UIImageView+HighlightedWebCache

缓存高亮图片

  • UIImageView+WebCache

主要用到这个,加载及缓存UIImageView的图片,和其他的拓展都是与用户直接打交道的

  • UIView+WebCacheOperation

缓存的操作,有缓存,取消操作,移除缓存

其中,最重要的三个类就是SDWebImageDownloader、SDImageCache、SDWebImageManager

框架结构:
在这里插入图片描述

  • UIImageView+WebCache和UIButton+WebCache直接为表层的 UIKit框架提供接口
  • SDWebImageManger负责处理和协调SDWebImageDownloader和SDWebImageCache, 并与 UIKit层进行交互。
  • SDWebImageDownloaderOperation真正执行下载请求;最底层的两个类为高层抽象提供支持。

四,流程

在SDWebImage的使用例子中,给UIImageView设置图片的代码是

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:[_objects objectAtIndex:indexPath.row]]placeholderImage:[UIImage imageNamed:@"placeholder"] options:indexPath.row == 0 ? SDWebImageRefreshCached : 0];

SDWebImage只用一行代码,就可以实现网络图片的加载和缓存,这行代码的背后,会执行下列操作

  1. UIImageView+WebCache categories会从管理类SDWebImageManager找图片,并刷新UIImageView。
  2. SDWebImagemanager向从缓存类SDImageCache找URL对应的图片缓存,如果没有找到,起用SDWebImageDownloader下载图片。
  3. 缓存类SDImageCache会先在内存NSCache中找图片,如果内存中没有找到,就在磁盘上找,在磁盘找到了,把图片放入内存。
  4. SDWebImageDownLoader会创建一个SDWebImageDownloaderOperation操作队列下载图片,下载后缓存在内存和磁盘上。
  5. SDWebImageDownloaderOperation操作队列使用NSURLconnection在后台发起请求,下载图片,反馈进度和加载图片。

五,主要类的源码

SDImageCache

SDImageCache类管理着内存缓存,提供了一个方便的单例shareImageCache。如果不想使用default缓存空间,而想创建你自己的SDImageCache对象来指定其他命名空间初始化来管理缓存

 1. (SDImageCache *)sharedImageCache {static dispatch_once_t once;static id instance;dispatch_once(&once, ^{instance = [self new];});return instance;
}

经典的iOS单例,使用dispatch_once防止多线程环境下生成多个实例。

- (id)init {return [self initWithNamespace:@"default"];
}- (id)initWithNamespace:(NSString *)ns {NSString *path = [self makeDiskCachePath:ns];return [self initWithNamespace:ns diskCacheDirectory:path];
}- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {if ((self = [super init])) {NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];// 初始化PNG标记数据kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];// 创建ioQueue串行队列负责对硬盘的读写_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);// 初始化默认的最大缓存时间_maxCacheAge = kDefaultCacheMaxCacheAge;// 初始化内存缓存,详见接下来解析的内存缓存类_memCache = [[AutoPurgeCache alloc] init];_memCache.name = fullNamespace;// 初始化磁盘缓存if (directory != nil) {_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];} else {NSString *path = [self makeDiskCachePath:ns];_diskCachePath = path;}// 设置默认解压缩图片_shouldDecompressImages = YES;// 设置默认开启内存缓存_shouldCacheImagesInMemory = YES;// 设置默认不使用iCloud_shouldDisableiCloud = YES;dispatch_sync(_ioQueue, ^{_fileManager = [NSFileManager new];});#if TARGET_OS_IPHONE// app事件注册,内存警告事件,程序被终止事件,已经进入后台模式事件,详见后文的解析:app事件注册。[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(clearMemory)name:UIApplicationDidReceiveMemoryWarningNotificationobject:nil];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(cleanDisk)name:UIApplicationWillTerminateNotificationobject:nil];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(backgroundCleanDisk)name:UIApplicationDidEnterBackgroundNotificationobject:nil];
#endif}return self;
}

内存缓存类

@interface AutoPurgeCache : NSCache
@end@implementation AutoPurgeCache- (id)init
{self = [super init];if (self) {[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];}return self;
}- (void)dealloc
{[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];}@end

这就是上面初始化的内存缓存类AutoPurgeCache,使用NSCache派生得到。整个类只有一个逻辑,就是添加观察者,在内存警告时,调用NSCache的@selector(removeAllObjects),清空内存缓存。

app事件注册
app事件注册使用经典的观察者模式,当观察到内存警告,程序被终止,程序进入后台这些事件时,程序将自动调用相应的方法处理。

内存警告
当收到UIApplicationDidReceiveMemoryWarningNotification时,调用的@selector(clearMemory),在方法中调用内存缓存类AutoPurgeCache的方法removeAllObject。

程序被终止
当收到UIApplicationWillTerminateNotification时,SDImageCache将会使用ioQueue异步地清理磁盘缓存。
具体清理逻辑:

  1. 先清除已超过最大缓存时间的缓存文件(最大缓存时间默认为一星期)
  2. 在第一轮清除的过程中保存文件属性,特别是缓存文件大小
  3. 在第一轮清除后,如果设置了最大缓存并且保留下来的磁盘缓存文件仍然超过了配置的最大缓存,那么进行第二轮以大小为基础的清除。
  4. 首先删除最老的文件,直到达到期望的总的缓存大小,即最大缓存的一半。

程序进入后台

当收到UIApplicationDidEnterBackgroundNotification时,在手机系统后台进行如上面描述的异步磁盘缓存清理。这里利用Objective-C的动态语言特性,得到UIApplication的单例sharedApplication,使用sharedApplication开启后台任务cleanDiskWithCompletionBlock:。

缓存中取图片

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {if (!doneBlock) {// 如果 doneBlock 为空,则直接返回 nilreturn nil;}if (!key) {// 如果 key 为空,则执行 doneBlock,并返回 nildoneBlock(nil, SDImageCacheTypeNone);return nil;}// 首先查询内存缓存UIImage *image = [self imageFromMemoryCacheForKey:key];if (image) {// 如果内存缓存中存在该图片,则执行 doneBlock,并返回 nildoneBlock(image, SDImageCacheTypeMemory);return nil;}NSOperation *operation = [NSOperation new];dispatch_async(self.ioQueue, ^{if (operation.isCancelled) {return;}@autoreleasepool {// 从磁盘缓存中获取图片UIImage *diskImage = [self diskImageForKey:key];if (diskImage && self.shouldCacheImagesInMemory) {// 如果磁盘缓存中存在该图片且内存缓存开启,则将图片保存到内存缓存中//将图片保存到BSCache,并把图像像素大小作为该对象的cost值NSUInteger cost = SDCacheCostForImage(diskImage);[self.memCache setObject:diskImage forKey:key cost:cost];}dispatch_async(dispatch_get_main_queue(), ^{// 在主队列中执行 doneBlock,并返回磁盘缓存中的图片doneBlock(diskImage, SDImageCacheTypeDisk);});}});return operation;
}- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {// 从内存缓存中获取指定 key 的图片return [self.memCache objectForKey:key];
}FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {// 计算图片在内存缓存中的成本(cost),即图片像素大小return image.size.height * image.size.width * image.scale * image.scale;
}

传入的Block定义是:

typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);

先从内存中取图片,内存中没有的时候再从磁盘中取,通过Block返回取到的图片和获取图片的方式。
SDImageCacheType的定义如下:

typedef NS_ENUM(NSInteger, SDImageCacheType)
{/*** The image wasn't available the SDWebImage caches, but was downloaded from the web.*/SDImageCacheTypeNone,/*** The image was obtained from the disk cache.*/SDImageCacheTypeDisk,/*** The image was obtained from the memory cache.*/SDImageCacheTypeMemory
};

当然,也可能磁盘中也没有缓存,此时doneBlock中的diskImage的值是nil,处理方式doneBlock将在SDWebImageManager讲到。

缓存中取图片

static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
static NSData *kPNGSignatureData = nil;BOOL ImageDataHasPNGPreffix(NSData *data);BOOL ImageDataHasPNGPreffix(NSData *data) {NSUInteger pngSignatureLength = [kPNGSignatureData length];if ([data length] >= pngSignatureLength) {if ([[data subdataWithRange:NSMakeRange(0, pngSignatureLength)] isEqualToData:kPNGSignatureData]) {return YES;}}return NO;
}- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {if (!image || !key) {return;}// if memory cache is enabled//如果启用了内存缓存if (self.shouldCacheImagesInMemory) {NSUInteger cost = SDCacheCostForImage(image);[self.memCache setObject:image forKey:key cost:cost];}if (toDisk) {dispatch_async(self.ioQueue, ^{NSData *data = imageData;if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE// 我们需要判断图片是PNG还是JPEG格式。PNG图片很容易检测,因为它们拥有一个独特的签名<http://www.w3.org/TR/PNG-Structure.html>。PNG文件的前八字节经常包含如下(十进制)的数值:137 80 78 71 13 10 26 10// 如果imageData为nil(也就是说,如果试图直接保存一个UIImage或者图片是由下载转换得来)并且图片有alpha通道,我们将认为它是PNG文件以避免丢失透明度信息。int alphaInfo = CGImageGetAlphaInfo(image.CGImage);BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||alphaInfo == kCGImageAlphaNoneSkipFirst ||alphaInfo == kCGImageAlphaNoneSkipLast);BOOL imageIsPng = hasAlpha;// 但是如果我们有image data,我们将查询数据前缀if ([imageData length] >= [kPNGSignatureData length]) {imageIsPng = ImageDataHasPNGPreffix(imageData);}if (imageIsPng) {data = UIImagePNGRepresentation(image);}else {data = UIImageJPEGRepresentation(image, (CGFloat)1.0);}
#elsedata = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif}if (data) {if (![_fileManager fileExistsAtPath:_diskCachePath]) {[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];}// 获得对应图像key的完整缓存路径NSString *cachePathForKey = [self defaultCachePathForKey:key];// 转换成NSUrlNSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];[_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];// 关闭iCloud备份if (self.shouldDisableiCloud) {[fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];}}});}
}- (void)storeImage:(UIImage *)image forKey:(NSString *)key {[self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:YES];
}- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk {[self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:toDisk];
}

存储一个图片到缓存中,可以使用方法storeImage:forKey:method:,默认,图片会存储到内存缓存中,也会异步地保存到磁盘缓存中,如果只想使用内存缓存,可以使用另外一个方法storeImage:forKey:toDisk,第三个参数传入false值就好了。

SDWebImageDownloader

SDWebImageDownloader是下载管理类,是一个单例类,图片的下载在一个NSOperationQueue队列中完成。

@property (strong, nonatomic) NSOperationQueue *downloadQueue;

下载图片的消息是

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)urloptions:(SDWebImageDownloaderOptions)optionsprogress:(SDWebImageDownloaderProgressBlock)progressBlockcompleted:(SDWebImageDownloaderCompletedBlock)completedBlock;

该方法会创建一个SDWebImageDownloaderOperation操作队列来执行下载操作。传入的两个Block用于网络下载的回调,progressBlock为下载进度回调,completeBlock为下载完成回调,回调信息存储在URLCallblacks中,为保证只有一个线程操作URLCallbacks,SDWebImageloader把这些操作放入一个barrierQueue队列中。

_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
// 创建一个并发队列 _barrierQueue,用于执行同步栅栏操作。// 这个方法用于添加下载进度回调和完成回调。
// 参数:
// - progressBlock:下载进度的回调块
// - completedBlock:下载完成的回调块
// - url:要添加回调的 URL
// - createCallback:回调创建时的块
// 在这个方法中,我们使用了同步栅栏操作来确保在添加回调时线程安全。
// - 首先,我们检查 self.URLCallbacks 字典中是否存在以 url 为键的数组。如果不存在,则创建一个空的数组,并将 first 标志设置为 YES。
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback
{···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// 处理对同一 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;// 接下来,我们将进度回调和完成回调添加到以 url 为键的字典中。// - 首先,我们创建一个可变字典 callbacks。// - 如果 progressBlock 存在,则将其拷贝到 callbacks 中。// - 如果 completedBlock 存在,则将其拷贝到 callbacks 中。// - 然后,我们获取 self.URLCallbacks[url] 的可变数组,并将 callbacks 添加到该数组中。// - 最后,我们将更新后的数组重新赋值给 self.URLCallbacks[url]。if (first){createCallback();}// 最后,在同步栅栏操作的闭包中,我们检查 first 标志。如果该标志为 YES,则调用 createCallback 块。}); 
}

SDWebImageDownloader还提供了两种下载任务调度方法(先进先出和后进先出)

typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder)
{/*** Default value. All download operations will execute in queue style (first-in-first-out).*/SDWebImageDownloaderFIFOExecutionOrder,/*** All download operations will execute in stack style (last-in-first-out).*/SDWebImageDownloaderLIFOExecutionOrder
};

通过修改execution可改变下载方式:

@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock{···[wself.downloadQueue addOperation:operation];if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder){// Emulate LIFO execution order by systematically adding new operations as last operation's dependency[wself.lastAddedOperation addDependency:operation];wself.lastAddedOperation = operation;}}];···
}

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation是下载操作队列,继承自NSOperation,并采用了SDWebImageOperation协议,该协议只有一个cancel方法。只暴露了一个方法。

- (id)initWithRequest:(NSURLRequest *)requestoptions:(SDWebImageDownloaderOptions)optionsprogress:(SDWebImageDownloaderProgressBlock)progressBlockcompleted:(SDWebImageDownloaderCompletedBlock)completedBlockcancelled:(SDWebImageNoParamsBlock)cancelBlock;

该方法的progressBlock与completeBlockSDWebImageDownloader下载管理类对应。
SDWebImageDownloaderOperation使用startdone来控制状态,而不使用main图片的下载使用NSURLConnection,在协议中接收数据并回调Block通知Block通知下载进度和下载完成。

SDWebImageManager

SDWebImageManager是一个单例管理类负责协调图片的缓存和图片的下载,隐藏在UIImageView + WebCache背后,是对SDImageCacheSDWebImageDownloader的封装。

@property (strong, nonatomic, readwrite) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;

在一般使用中,我们并不直接使用SDImageCache和SDWebImageDownloader,而使用SDWebImageManager的核心方法是

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)urloptions:(SDWebImageOptions)optionsprogress:(SDWebImageDownloaderProgressBlock)progressBlockcompleted:(SDWebImageCompletionWithFinishedBlock)completedBlock

SDWebImageManager是单例使用的,分别维护了一个SDImageCache实例和一个SDWebImageDownloader实例。对象方法分别是

// 初始化SDWebImageManager单例,在init方法中已经初始化了cache单例和downloader单例。
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
// 下载图片
- (id )downloadImageWithURL:(NSURL *)urloptions:(SDWebImageOptions)optionsprogress:(SDWebImageDownloaderProgressBlock)progressBlockcompleted:(SDWebImageCompletionWithFinishedBlock)completedBlock;
// 缓存给定URL的图片
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
// 取消当前所有的操作
- (void)cancelAll;
// 监测当前是否有进行中的操作
- (BOOL)isRunning;
// 监测图片是否在缓存中, 先在memory cache里面找  再到disk cache里面找
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
// 监测图片是否缓存在disk里
- (BOOL)diskImageExistsForURL:(NSURL *)url;
// 监测图片是否在缓存中,监测结束后调用completionBlock
- (void)cachedImageExistsForURL:(NSURL *)urlcompletion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
// 监测图片是否缓存在disk里,监测结束后调用completionBlock
- (void)diskImageExistsForURL:(NSURL *)urlcompletion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回给定URL的cache key
- (NSString *)cacheKeyForURL:(NSURL *)url;

Categories(分类)

UIImageView+WebCache
在Categories目录下实现了多个分类,实现方法是一致的。其中使用最多的是UIImageView+WebCache,针对UIIMageView扩展了一些方法。

//UIImageView+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)url {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {[self sd_internalSetImageWithURL:urlplaceholderImage:placeholderoptions:optionsoperationKey:nilsetImageBlock:nilprogress:progressBlockcompleted:completedBlock];
}- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:key];[self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];    
}

在使用调用的方法是

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock

该方法依赖与SDWebIamgeManager,从SDWebImageManager管理类中获取图片并刷新显示,至于图片是从缓存中得到还是从网络上下载的对UIImageView是透明的。

UIView+WebCache
新版本还给UIView增加了分类,即UIView+WebCache,最终上述方法会走到下面的方法去具体操作,比如下载图片等。

//UIView+WebCache
- (void)sd_internalSetImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsoperationKey:(nullable NSString *)operationKeysetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}//参数一:url            图像的url。
//参数二:placeholder    最初要设置的图像,直到图像请求完成。
//参数三:options        下载图像时要使用的选项。
//参数四:context        上下文包含用于执行指定更改或过程的不同选项
//参数五:setImageBlock  块用于自定义设置图像代码。
//参数六:progressBlock  下载图像时调用的块。进度块是在后台队列上执行的。
/*这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。
如果发生错误,image参数为nil,第三个参数可能包含一个NSError。
第四个参数是一个“SDImageCacheType”enum,表示图像是从本地缓存、内存缓存还是网络检索到的。
第五个参数通常总是“YES”。但是,如果你提供SDWebImageAvoidAutoSetImage与SDWebImageProgressiveLoad选项,以启用渐进下载和设置自己的映像。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
最后一个参数是原始图像URL。*/

六,SDWebImage的使用

1.使用UIImageVie+WebCache category来加载UItableView中的cell的图片

2.使用Blocks,采用这个方案可以在网络图片加载过程中得知图片的下载进度和图片加载成功与否

[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img1.cache.netease.com/catchpic/5/51/5132C377F99EEEE927697E62C26DDFB1.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {// ... completion code here ... }];

3.使用SDWebImageManager,SDWebImageManager为UIImageView+WebCache category的实现提供接口。

SDWebImageManager *manager = [SDWebImageManager sharedManager] ;
[manager downloadImageWithURL:imageURL options:0 progress:^(NSInteger   receivedSize, NSInteger expectedSize) { // progression tracking code}  completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,   BOOL finished, NSURL *imageURL) { if (image) { // do something with image}
}];

4.加载图片还有使用SDWebImageDownloader和SDImageCache方式

5.key的来源

// 利用Image的URL生成一个缓存时需要的key.
// 这里有两种情况,第一种是如果检测到cacheKeyFilter不为空时,利用cacheKeyFilter来处理URL生成一个key.
// 如果为空,那么直接返回URL的string内容,当做key.
- (NSString *)cacheKeyForURL:(NSURL *)url {if (self.cacheKeyFilter) {return self.cacheKeyFilter(url);}else {return [url absoluteString];}
}

SDWebImage的流程

在这里插入图片描述

SDWebImage解析

  1. 入口setImageWithURL:placeholderImage:options:会先把placeholderImage显示,然后 SDWebImageManager根据URL开始处理图片。
  2. 进入SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo:。
  3. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate回调imageCache:didFindImage:forKey:userInfo:到SDWebImageManager。
  4. SDWebImageManagerDelegate回调webImageManager:didFinishWithImage:到UIImageView+WebCache等前端展示图片。
  5. 如果内存缓存中没有,生成NSInvocationOperation添加到队列开始从硬盘查找图片是否已经缓存。
  6. 根据URLKey在硬盘缓存目录下尝试读取图片文件。这一步是在NSOperation进行的操作,所以回主线程进行结果回调notifyDelegate:。
  7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate回调imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
  8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调imageCache:didNotFindImageForKey:userInfo:
  9. 共享或重新生成一个下载器SDWebImageDownloader开始下载图片。
  10. 图片下载由NSURLConnection来做,实现相关delegate来判断图片下载中、下载完成和下载失败。
  11. connection:didReceiveData:中利用ImageIO做了按图片下载进度加载效果。
  12. connectionDidFinishLoading:数据下载完成后交给SDWebImageDecoder做图片解码处理。
  13. 图片解码处理在一个NSOperationQueue完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  14. 在主线程notifyDelegateOnMainThreadWithInfo:宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给SDWebImageDownloader
  15. imageDownloader:didFinishWithImage:回调给SDWebImageManager告知图片下载完成。
  16. 通知所有的downloadDelegates下载完成,回调给需要的地方展示图片。
  17. 将图片保存到SDImageCache中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独NSInvocationOperation完成,避免拖慢主线程。
  18. SDImageCache在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
  19. SDWebImage也提供了UIButton+WebCache和MKAnnotationView+WebCache,方便使用。
  20. SDWebImagePrefetcher可以预先下载图片,方便后续使用。

从上面流程可以看出,当你调用setImageWithURL:方法的时候,他会自动去给你干这么多事,当你需要在某一具体时刻做事情的时候,你可以覆盖这些方法。比如在下载某个图片的过程中要响应一个事件,就覆盖这个方法:

// 覆盖方法,指哪打哪,这个方法是下载imagePath2的时候响应
SDWebImageManager *manager = [SDWebImageManager sharedManager];[manager downloadImageWithURL:imagePath2 options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {NSLog(@"显示当前进度");
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {NSLog(@"下载完成");
}];

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/309201.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

InnoDB中高度为3的B+树最多可以存多少数据?

参考&#xff1a; &#x1f525;我说MySQL每张表最好不超过2000万数据&#xff0c;面试官让我回去等通知&#xff1f; - 掘金 考虑到磁盘IO是非常高昂的操作&#xff0c;计算机操作系统做了预读的优化&#xff0c;当一次IO时&#xff0c;不光把当前磁盘地址的数据&#xff0c;…

Linux 快问快答

如果对于找 Java 后端开发的话&#xff0c;我感觉会这几个差不多了&#xff0c;面试官应该不会问的这么详细吧。一般就问问 Linux 的几个常用的命令&#xff0c;然后做一些简单的性能排查就好了。如果面试被问到另外的问题&#xff0c;那我再补充进来&#xff0c;现在先掌握这么…

stm32开发之threadx+modulex组合开发使用记录

前言 参考博客 论坛官方资料: 微软开发板核心芯片使用的是stm32f407zgtx&#xff0c;烧录工具使用的是jlink模块的构建使用的是脚本进行构建网上针对modulex的资料较少&#xff0c;这里做个记录 项目结构 逻辑框架 主程序代码 主函数 /** Copyright (c) 2024-2024&#xff0…

Abstract Factory抽象工厂模式详解

模式定义 提供一个创建一系列相关或互相依赖对象的接口&#xff0c;而无需指定它们具体的类。 代码示例 public class AbstractFactoryTest {public static void main(String[] args) {IDatabaseUtils iDatabaseUtils new OracleDataBaseUtils();IConnection connection …

shell 调用钉钉通知

使用场景&#xff1a;机器能访问互联网&#xff0c;运行时间任务后通知使用 钉钉建立单人群 手机操作&#xff0c;只能通过手机方式建立单人群 电脑端 2. 配置脚本 #!/bin/bash set -e## 上图中 access_token字段 TOKEN KEYWORDhello # 前文中设置的关键字 function call_…

Java入门教程||Java 变量

Java 变量 Java教程 - Java变量 变量由标识符&#xff0c;类型和可选的初始化程序定义。变量还具有范围&#xff08;可见性/生存期&#xff09;。 Java变量类型 在Java中&#xff0c;必须先声明所有变量&#xff0c;然后才能使用它们。变量声明的基本形式如下所示&#xff1…

【Web】DASCTF X GFCTF 2022十月挑战赛题解

目录 EasyPOP hade_waibo EasyLove BlogSystem EasyPOP 先读hint.php sorry.__destruct -> secret_code::secret() exp: $anew sorry(); $bnew secret_code(); $a->password"suibian"; $a->name"jay"; echo serialize($a); 真暗号啊&…

RMAN数据迁移方案

数据迁移 Oracle环境检查 开启归档 1.首先关闭数据库 shutdown immediate; 2.打开mount状态 startup mount; 3.更改数据库为归档模式 alter database archivelog; 4.打开数据库 alter database open; 5.再次检查 archive log list; 查看构造的表和数据 由于数据会有中文&…

【微信小程序——案例——本地生活(列表页面)】

案例——本地生活&#xff08;列表页面&#xff09; 九宫格中实现导航跳转——以汽车服务为案例&#xff08;之后可以全部实现页面跳转——现在先实现一个&#xff09; 在app.json中添加新页面 修改之前的九宫格view改为navitage 效果图&#xff1a; 动态设置标题内容—…

SpringMVC--获取请求参数 / 域对象共享数据

目录 1. SpringMVC 获取请求参数 1.1. 通过ServletAPI获取 1.2. 控制器方法形参获取 1.3. RequestParam 1.4. RequestHeader 1.5. CookieValue 1.6. 通过POJO获取请求参数 1.7. 解决获取请求参数的乱码问题 2. 域对象共享数据 2.1. 三大域对象 2.2. 准备工作 2.3. S…

Linux awk

文章目录 1. 基础用法2. awk字符获取和筛选获取CPU/MEM占用率将awk指令包封脚本 3.awk条件与循环语句4.awk调用函数 1. 基础用法 操作粒度更加精细&#xff0c;可以以特殊字符&#xff08;: 空格等&#xff09;分割为列再进一步操作。例如 ps -aux获取到自己的进程后想进一步…

React+TS项目搭建

使用webpack5搭建ReactTS项目 一.初始化项目 初始化一个基本的reactts项目,首先创建一个项目文件夹,输入初始化命令 npm init -y 初始化完成后生成package.json文件,之后需要在项目下新增以下所示目录结构和文件 ├── build | ├── webpack.base.js # 公共配置 | ├…

Docker+Nginx部署vue项目

这篇文章给大家分享一下如何使用DockerNginx部署前端vue项目。 第一步&#xff1a;创建vue项目 执行这个命令&#xff0c;创建一个vue项目 npm create vue3将vue项目打包 npm run build此时会看到vue工程中生成了一个dist文件&#xff0c;我们将他上传到服务器中。 第二步…

微服务之LoadBalancer负载均衡服务调用

一、概述 1.1什么是负载均衡 LB&#xff0c;既负载均衡&#xff08;Load Balancer&#xff09;,是高并发、高可用系统必不可少的关键组件&#xff0c;其目标是尽力将网络流量平均分发到多个服务器上&#xff0c;以提高系统整体的响应速度和可用性。 负载均衡的主要作用 高并发…

第十二讲 查询计划 优化

到目前为止&#xff0c;我们一直在说&#xff0c;我们得到一个 SQL 查询&#xff0c;我们希望可以解析它&#xff0c;将其转化为某种逻辑计划&#xff0c;然后生成我们可以用于执行的物理计划。而这正是查询优化器【Optimizer】的功能&#xff0c;对于给定的 SQL &#xff0c;优…

基于RT-Thread(RTT)的BMP280气压计驱动(I2C通信)

前言 本文基于RTT操作系统使用STM32F401RET6驱动BMP280气压计模块&#xff0c;使用I2C协议通信 一、新建工程 二、添加软件包 三、添加这个包 四、打开CubeMX 五、配置时钟源&#xff0c;使用外部晶振 六、配置串行下载口 七、打开I2C&#xff0c;我这里使用的是I2C2&#x…

大模型面试准备(十八):使用 Pytorch 从零实现 Transformer 模型

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何备战、面试常考点分享等热门话题进行了深入的讨论。 合集在这…

VRRP虚拟路由实验(华为)

思科设备参考&#xff1a;VRRP虚拟路由实验&#xff08;思科&#xff09; 一&#xff0c;技术简介 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;是一种网络协议&#xff0c;用于实现路由器冗余&#xff0c;提高网络可靠性和容错能力。VRRP允许多台路由器…

Windows Server 2016虚拟机安装教程

一、VMware Workstation虚拟机软件的下载 官网下载入口&#xff1a;​​​​​​Download VMware Workstation Pro - VMware Customer Connect​​​​​ 下载好之后自己看着提示安装软件就好. 二、镜像文件的下载 下载网站入口&#xff1a;MSDN, 我告诉你 - 做一个安静…

架构师系列-搜索引擎ElasticSearch(六)- 映射

映射配置 在创建索引时&#xff0c;可以预先定义字段的类型&#xff08;映射类型&#xff09;及相关属性。 数据库建表的时候&#xff0c;我们DDL依据一般都会指定每个字段的存储类型&#xff0c;例如&#xff1a;varchar、int、datetime等&#xff0c;目的很明确&#xff0c;就…