SDWebImage 简介
SDWebImage 是 iOS 开发中主流的图像加载库,它帮我们处理内存缓存、磁盘缓存与及图像加载的一系列操作。使用起来方便快捷,让我们更好的专注于业务逻辑的开发。
组织结构
SDWebImage 框架组成如下:
功能快速一览图:
SDWebImageCompat 做机型适配的。SDWebImageManager 管理缓存和下载的一个类。SDImageCache 处理缓存和内存的类。SDWebImageDownloader 异步下载器专用和优化图像加载。SDWebImagePrefetcher 图片的预加载。
源码解析
SDImageCache
SDImageCache 是继承自 NSObject 的。做了 cache 的一些基本配置和 cache 的管理。如 cache 的大小,cache 的有效期,添加 cache,删除 cache 等。
cache 的类型如下:
typedef NS_ENUM(NSInteger, SDImageCacheType) {
/**
* The image wasn’t available the SDWebImage caches, but was downloaded from the web.(不缓存,从 web 加载)
*/
SDImageCacheTypeNone,
/**
* The image was obtained from the disk cache.(磁盘缓存)
*/
SDImageCacheTypeDisk,
/**
* The image was obtained from the memory cache.(内存缓存)
*/
SDImageCacheTypeMemory
};
内部的 AutoPurgeCache 是继承自 NSCache 的,主要用途是在收到系统的 UIApplicationDidReceiveMemoryWarningNotification 通知时,清理内存缓存。
此外,SDWebCache 的内存缓存也是使用的 NSCache. 设置缓存的核心方法如下:
– (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
// We need to determine if the image is a PNG or a JPEG
// PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)
// The first eight bytes of a PNG file always contain the following (decimal) values:
// 137 80 78 71 13 10 26 10
// If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download)
// and the image has an alpha channel, we will consider it PNG to avoid losing the transparency
int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL imageIsPng = hasAlpha;
// But if we have an image data, we will look at the preffix
if ([imageData length] >= [kPNGSignatureData length]) {
imageIsPng = ImageDataHasPNGPreffix(imageData);
}
if (imageIsPng) {
data = UIImagePNGRepresentation(image);
}
else {
data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
}
#else
data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
}
[self storeImageDataToDisk:data forKey:key];
});
}
}
从代码可以看出,先设置的内存缓存,再设置的磁盘缓存。
– (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {
if (key == nil) {
return;
}
if (self.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
if (fromDisk) {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
completion();
}
}
删除缓存的时候也是先删除内存缓存,在删除磁盘缓存。
UIImageView+WebCache
通过 UIImageView 集成 SDWebImage 异步下载和缓存远程图像。下载图片的核心方法如下:
– (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_cancelCurrentImageLoad];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{ // 保证是在主线程中设置图片
self.image = placeholder;
});
}
if (url) {
// check if activityView is enabled or not
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
[wself removeActivityIndicator];
if (!wself) return;
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:@”UIImageViewImageLoad”];
} else {
dispatch_main_async_safe(^{
[self removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @”Trying to load a nil url”}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
从代码可以看出,代码使用时 runtime 为分类添加属性,设置 placeholder 一定是在住线程中进行的,图片真正的下载操作,使用的是 SDWebImageOperation。
图片加载的时序图:
而且这里有两个值得我们学习的宏定义:
#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_sync(dispatch_get_main_queue(), block);\
}
保证同步线程是在主线程执行的。
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
保证异步线程是在主线程执行的。
SDWebImageManager
SDWebImageManager 是整个框架的一个核心类,它把 SDImageCache 和 SDWebImageDownloader 结合在一起,同时管理图片的下载和缓存操作。
SDWebImageOptions 这个 options,提供了我们下载图片时的很多可选操作。示例如下:
/**
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won’t keep trying.
* 默认情况下,当一个 URL 下载失败,该 URL 被列入黑名单,将不会继续尝试下载
* This flag disable this blacklisting.
* 此标志取消黑名单
*/
SDWebImageRetryFailed = 1 << 0,
/**
* By default, image downloads are started during UI interactions, this flags disable this feature,
* 默认情况下,在 UI 交互时也会启动图像下载,此标记取消这一功能
* leading to delayed download on UIScrollView deceleration for instance.
* 会延迟下载,UIScrollView 停止滚动之后再继续下载
* 下载事件监听的运行循环模式是 NSDefaultRunLoopMode
*/
SDWebImageLowPriority = 1 << 1,
/**
* This flag disables on-disk caching
* 禁用磁盘缓存
*/
SDWebImageCacheMemoryOnly = 1 << 2,
注:图片来源