shimenyimeng 2019-07-01
SDWebImage是iOS开发中主流的图像加载库,它帮我们处理内存缓存、磁盘缓存与及图像加载的一系列操作。使用起来方便快捷,让我们更好的专注于业务逻辑的开发。
SDWebImage框架组成如下:
功能快速一览图:
SDWebImageCompat
做机型适配的。SDWebImageManager
管理缓存和下载的一个类。SDImageCache
处理缓存和内存的类。SDWebImageDownloader
异步下载器专用和优化图像加载。SDWebImagePrefetcher
图片的预加载。
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
集成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是整个框架的一个核心类,它把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,
注:图片来源