SDWebImage
SDWebImage具有缓存支持的异步映像下载程序。并添加了像UI元素分类类UIImageView、UIButton、MKAnnotationView,可以直接为这些UI元素添加图片。
日常使用
在日常的使用中,通常是加载网络图片到UIImageView上展示,所以一般在需要使用SDWebImage的文件中只引用#import "UIImageView+WebCache.h"头文件。
最简单的加载方式是只加载图片地址:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:imageView];
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]];
当然,SDWebImage也提供了其他的加载方法,不过点击方法进入查看后,发现最终都是调用其全能方法:
- (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
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
紧接着我们点击进入全能方法中:
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
可以发现,全能方法并没有什么实际的实现,只是对另一个方法的封装。
一些主要功能
- 对
UIImageView 、UIButton 、MKAnnotationView 添加Web图像和告诉缓存管理 - 异步图像下载器
- 具有自动缓存到期处理的异步内存+磁盘映像缓存
- 背景图像解压缩
- 对动画图像的支持
- 可以自定义和组合的转换,可在下载后立即应用于图像
- 可以自定义加载器(如照片库)来扩展图像加载功能
- 加载中的
indicator 显示 - 保证不会下载相同的
URL - 下载过程或者资源保存过程用到了
GCD 和ARC - 提前将获取到的图片放到主线程,保证不会阻塞主线程
图片加载全过程
- 我们在使用
SDWebImage 时调用了[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]]; 这个简单的分类方法,然后就静静的等着图片被设置到UIImageView 类对象上。 - 经过一系列调用,我们首先来到
UIView+WebCache 分类中,在这个分类中,首先保障了图片加载的唯一性,然后就开始了核心的加载操作。 - 接着就进入了
SDWebImageManager 类中,在这个类中,首先去查找是否有缓存,没有缓存的话才去服务器下载。 - 想要查找缓存我们要进入
SDImageCache 这个类中,在这个类中,首先去内存中查看是否有对应的缓存,如果没有再去硬盘中查找是否有对应的缓存,但是从硬盘中获取的是图片的数据,要想获得图片还要经历解码、缩放和解压。当然如果都没有缓存的话就去下载。 - 负责下载的是
SDWebImageDownloader 这个类,在这个类中,将图片的下载操作封装成了自定义的一个类SDWebImageDownloaderOperation ,然后添加到了操作队列中。 - 当操作队列调用这个操作时,会调用操作对象的
- (void)start 方法,在重写的这个方法中,生成了任务对象dataTask ,并调用resume 开始执行任务。 - 因为
SDWebImageDownloaderOperation 类遵守了dataTask 对象的协议,所以dataTask 执行的结果会通过代理方法进行回调。在代理方法中,获取并保存了服务器返回的数据,并在任务执行结束后,对数据进行解码、缩放和解压。处理完成后就进行回调。 - 通过重重回调,要回调的数据沿着
SDWebImageDownloaderOperation ->SDWebImageDownloader ->SDWebImageManager ->UIView+WebCache 一路流动,其中流动到SDWebImageManager 中时对图片进行了缓存,最后在UIView+WebCache 中为UIImageView 设置了处理好的图片。
源码分析
架构图
从SDWebImage中提供的架构图中我们可以大概的看出,整个库分为两层,Top Level、Base Module。
Top Level :当UIImageView 调用加载image 方法,会进入SDWebImage 中的UIImageView 分类,在分类中调用负责加载UIImage 的核心代码块ImageManager ,其主要负责调度Image Cache/Image Loader ,这两者分别从缓存或者网络端加载图片,并且又进行了细分。Cache中获取图片业务,拆分到了memory/disk 两个分类中;Image Loader 中又分为从网络端获取或者从系统的Photos中获取。Base Module :获取到图片的二进制数据处理,二进制解压缩,二进制中格式字节判断出具体的图片类型。
结构
(设计思路借鉴:提供多种接口,到底层调用到同一个方法,减少调用方对可选参数的传递)
UIImageView+WebCache 和UIButton+WebCache 直接为表层的 UIKit框架提供接口,SDWebImageManger (SDWebImageManager 是SDWebImage 的核心类,也是我们经常接触到的类)负责处理和协调SDWebImageDownloader 和SDWebImageCache , 并与UIKit层进行交互。SDWebImageDownloaderOperation 真正执行下载请求;SDWebImageCompat 是最基础的配置文件,为了兼容苹果各个平台SDWebImageDecoder sd解码图片这个类其实是UIImage 的一个分类UIImage+ForceDecode ,主要用来解码UIImage
SDWebImageManager
SDWebImageManager是SDWebImage的核心类,也是我们经常接触到的类,我们将一起看下是如何实现的
1. SDWebImageOptions
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,
SDWebImageLowPriority = 1 << 1,
SDWebImageCacheMemoryOnly = 1 << 2,
SDWebImageProgressiveDownload = 1 << 3,
SDWebImageRefreshCached = 1 << 4,
SDWebImageContinueInBackground = 1 << 5,
SDWebImageHandleCookies = 1 << 6,
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
SDWebImageHighPriority = 1 << 8,
SDWebImageDelayPlaceholder = 1 << 9,
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImageAvoidAutoSetImage = 1 << 11,
SDWebImageScaleDownLargeImages = 1 << 12
};
2. SDWebImageManagerDelegate
@protocol SDWebImageManagerDelegate <NSObject>
@optional
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
@end
SDWebImageCompat
SDWebImage库中SDWebImageCompat是最基础的配置文件,为了兼容苹果各个平台。
SDWebImageCompat.h
#import <TargetConditionals.h>
#ifdef __OBJC_GC__
#error SDWebImage does not support Objective-C Garbage Collection
#endif
#if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_WATCH
#define SD_MAC 1
#else
#define SD_MAC 0
#endif
#if TARGET_OS_IOS || TARGET_OS_TV
#define SD_UIKIT 1
#else
#define SD_UIKIT 0
#endif
#if TARGET_OS_IOS
#define SD_IOS 1
#else
#define SD_IOS 0
#endif
#if TARGET_OS_TV
#define SD_TV 1
#else
#define SD_TV 0
#endif
#if TARGET_OS_WATCH
#define SD_WATCH 1
#else
#define SD_WATCH 0
#endif
#if SD_MAC
#import <AppKit/AppKit.h>
#ifndef UIImage
#define UIImage NSImage
#endif
#ifndef UIImageView
#define UIImageView NSImageView
#endif
#ifndef UIView
#define UIView NSView
#endif
#else
#if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
#error SDWebImage doesn't support Deployment Target version < 5.0
#endif
#if SD_UIKIT
#import <UIKit/UIKit.h>
#endif
#if SD_WATCH
#import <WatchKit/WatchKit.h>
#ifndef UIView
#define UIView WKInterfaceObject
#endif
#ifndef UIImageView
#define UIImageView WKInterfaceImage
#endif
#endif
#endif
#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#ifndef NS_OPTIONS
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
FOUNDATION_EXPORT UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
typedef void(^SDWebImageNoParamsBlock)(void);
FOUNDATION_EXPORT NSString *const SDWebImageErrorDomain;
#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
block();\
} else {\
dispatch_async(queue, block);\
}
#endif
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif
SDWebImageCompat.m
#import "SDWebImageCompat.h"
#import "UIImage+MultiFormat.h"
#if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif
#if !OS_OBJECT_USE_OBJC
#error SDWebImage need ARC for dispatch object
#endif
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
if (!image) {
return nil;
}
#if SD_MAC
return image;
#elif SD_UIKIT || SD_WATCH
if ((image.images).count > 0) {
NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];
if (animatedImage) {
animatedImage.sd_imageLoopCount = image.sd_imageLoopCount;
}
return animatedImage;
} else {
#if SD_WATCH
if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) {
#elif SD_UIKIT
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
#endif
CGFloat scale = 1;
if (key.length >= 8) {
NSRange range = [key rangeOfString:@"@2x."];
if (range.location != NSNotFound) {
scale = 2.0;
}
range = [key rangeOfString:@"@3x."];
if (range.location != NSNotFound) {
scale = 3.0;
}
}
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
image = scaledImage;
}
return image;
}
#endif
}
NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";
|