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.mzph.cn/news/816073.shtml

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

相关文章

Web前端 Javascript笔记3

1、垃圾回收机制 内存中的生命周期 1、内存分配 2、内存使用&#xff08;读写&#xff09; 3、内存回收&#xff0c;使用完毕之后&#xff0c;垃圾回收器完成 内存泄漏&#xff1a;该回收的&#xff0c;由于某些未知因素&#xff0c;未释放&#xff0c;叫做内存泄漏 栈&#xf…

Vue新手入门

1 Vue概述 官网:https://cn.vuejs.org/ 1、什么是Vue.js Vue.js 是目前最火的一个前端框架&#xff0c;React是最流行的一个前端框架&#xff08;React除了开发网站&#xff0c;还可以开发手机App&#xff0c; Vue语法也是可以用于进行手机App开发的&#xff0c;需要借助于W…

Bridge 桥接

意图 将抽象部分与其显示部分分离&#xff0c;使他们都可以独立地变化。 结构 其中&#xff1a; Abstraction定义抽象类的接口&#xff0c;维护一个指向Implementer类型对象的指针。RefinedAbstraction扩展由Abstraction定义的接口。Implementor定义实现类的接口&#xff0c…

React 19 的新增功能:Action Hooks

React 是前端开发领域最流行的框架之一。我喜欢 React 是因为它背后的团队和社区对它的热情。当社区提出新功能和改进的需求时&#xff0c;团队会倾听&#xff0c;React 的未来是令人兴奋和有趣的。 让我们来看一下 React 19 中令开发人员提升开发效率的新特性。对于每个钩子&…

关于项目打包

除了自己常用的那种方式&#xff0c;也可以直接在文件夹下执行命令。 如果当前项目聚合了其他子模块的话&#xff1a; 先清理&#xff0c;再打包&#xff0c;同时跳过测试 如果打包后&#xff0c;然后项执行某个模块&#xff0c;进入当前文件夹下直接java -jar 和jar包名执行就…

k8s:kubectl 命令设置简写启用自动补全功能

k8s&#xff1a;kubectl 命令设置简写&启用自动补全功能 1、设置kubectl命令简写2、启用kubectl自动补全功能 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Kubernetes&#xff08;K8s&#xff09;是一个强大的容器编排平台&#xff0…

恢复MySQL!是我的条件反射,PXB开源的力量...

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

力扣面试150 分发糖果 分步贪心

Problem: 135. 分发糖果 思路 &#x1f468;‍&#x1f3eb; 参考&#xff1a;代码随想录 一次是从左到右遍历&#xff0c;只比较右边孩子评分比左边大的情况。一次是从右到左遍历&#xff0c;只比较左边孩子评分比右边大的情况。 复杂度 时间复杂度: O ( n ) O(n) O(n) …

低成本,高效能:探索物联网新宠LoRa

LoRa是什么&#xff1f; LoRa是一种物联网无线传输技术&#xff0c;利用调制解调器实现低功耗远距离数据传输。其基本工作原理是通过基站发送数据到特定终端设备&#xff0c;实现双向数据传输。 LoRa无线传输技术是一种为低功耗和低成本设计的无线技术&#xff0c;用于实现远距…

【Linux】CentOS 7安装后没有图形界面

专栏文章索引&#xff1a;Linux 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 一、项目场景 二、问题描述 三、原因分析 四、解决方案 1.当前处于命令行界面&#xff0c;可以切换为图形界面 2.安装时没有安装图形界面&#xff0c;选择了Minimal Install 3.下…

鸿蒙端云一体化开发--开发云函数--适合小白体制

开发云函数 那什么是云函数&#xff1f;我们将来又怎么去使用这个云函数呢&#xff1f; 答&#xff1a;我们之前要编写一些服务端的业务逻辑代码&#xff0c;那现在&#xff0c;在这种端云一体化的开发模式下&#xff0c;我们是把服务端的业务逻辑代码&#xff0c;通过云函数来…

linux安装和使用-第一天

一. 安装linux系统 安装过程:略注意事项: 安装时一定一定一定不要选择有中文的目录包括镜像文件所在的目录,否则会发生各种问题,比如VMware Tools是灰色的.1. 安装ssh工具 (1) 安装命令 # 第一次安装系统需要更新一下apt的源,他维护了软件依赖关系,否则安装不了软件,每次安装…

MT3020 任务分配

思路&#xff1a;利用二分找到某个时间是满足“k个人可以完成” &#xff0c;并且时间最小。 因为尽量让后面的人做任务&#xff0c;所以从后往前排任务&#xff08;倒着分配&#xff09;。从后往前遍历任务&#xff0c;如果此人加上这个任务超出之前求得的时间&#xff0c;就…

快速入门深度学习9.1(用时20min)——GRU

速通《动手学深度学习》9.1 写在最前面九、现代循环神经网络9.1 门控循环单元&#xff08;GRU&#xff09;9.1.1. 门控隐状态9.1.1.1. 重置门和更新门9.1.1.2. 候选隐状态9.1.1.3. 隐状态 9.1.3 API简洁实现小结 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 20…

空指针与野指针的辨析

空指针 空指针不指向任何实际的对象或者函数&#xff0c;反过来&#xff0c;任何的对象或者函数也不可能是空指针。 在程序中得到空指针的办法就是使用预定义的NULL&#xff0c; int *ip NULL; 校验一个指针是否为空指针可以用 if (ip NULL) NULL是标准规定的宏定义&am…

h5 笔记4 表格与表单

<table></table>设置表格&#xff1b; <tr></tr>设置行数&#xff1b; <td></td>设置列数&#xff1b; <caption></caption>设置表格标题&#xff1b; <th></th>设置列标题。 直列&#xff1a;column&#xf…

Pytest精通指南(09)利用Fixture给函数设置别名

文章目录 前言测试用例默认显示传递一个参数传递多个参数 利用Fixture修改测试函数名称传递一个参数传递多个参数 验证ids和params长度不一致修改Fixture函数名称 前言 在 pytest 中&#xff0c;pytest.fixture 装饰器用于定义可以在多个测试函数中重用的设置和清理代码。 name…

虚拟机下CentOS7开启SSH连接

虚拟机下CentOS7开启SSH连接 自己在VMware中装了CentOS 6.3&#xff0c;然后主机&#xff08;或者说xshell&#xff09;与里面的虚拟机连不通&#xff0c;刚学习&#xff0c;一头雾水&#xff0c;查了半天&#xff0c;也不知道怎么弄。 在虚拟机&#xff08;Vmware Workstatio…

OSCP靶场--PayDay

OSCP靶场–PayDay 考点(公共exp文件上传密码复用sudo -l all提权) 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap -sV -sC 192.168.153.39 -p- -Pn --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-04-13 04:52 EDT Nmap scan report for 192…

C语言 | 字符函数和字符串函数

目录&#xff1a; 1. 字符分类函数 2. 字符转换函数 3. strlen的使用和模拟实现 4. strcpy的使用和模拟实现 5. strcat的使用和模拟实现 6. strcmp的使用和模拟实现 7. strncpy函数的使用 8. strncat函数的使用 9. strncmp函数的使用 10. strstr的使用 11. strtok函…