本文的思维导图(这里使用的是MindNode)
本文基于SDWebImage v3.8.2
目录
概述
-
结构
Base
Cache
Downloader
Categories
Utils
-
问题
SDScaledImageForKey的作用?
decodedImageWithImage的作用?
如何加载超大图片?
参考
概述
接口基于category
异步下载基于NSOperation
缓存基于Disk + memory二级缓存
异步解压缩等优化
支持png, jpg, gif和webp(默认不支持, 开启请参考How to support webp?)
默认情况下同一URL的网络图片不会被重复下载
通过对URL进行MD5保证同一URL不会重复下载
// SDImageCache.m line 182
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
如果要让同一URL重新下载, 需要配置如下option
[imageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRefreshCached];
结构
Base
SDWebImageCompat - 保证不同平台/版本/屏幕等兼容性的宏定义和内联
SDWebImageOperation - operation协议, 只定义了cancel operation这一个接口
Cache
- SDImageCache - 缓存 = Disk + memory二级缓存
其中:
Disk缓存maxAge默认是1 week
// SDImageCache.m line 37
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
// SDImageCache.m line 102
_maxCacheAge = kDefaultCacheMaxCacheAge;
Disk缓存清理策略是当app进入后台或销毁时
// SDImageCache.m line 136
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
memory缓存基于NSCache
// SDImageCache.m line 61
@property (strong, nonatomic) NSCache *memCache;
memory缓存清理策略是当app收到内存警告时
// SDImageCache.m line 131
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
Downloader
- SDWebImageDownloader - 异步图片下载管理: 管理下载队列, 管理operation, 管理网络请求, 处理结果和异常
// SDWebImageDownloader.m line 70
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
- SDWebImageDownloaderOperation - 实现了异步下载图片的NSOperation, 网络请求基于NSURLSession
// SDWebImageDownloaderOperation.h line 18
@interface SDWebImageDownloaderOperation : NSOperation
Categories
MKAnnotationView+WebCache - 为MKAnnotationView异步加载图片
NSData+ImageContentType - 通过image data判断当前图片的格式
// NSData+ImageContentType.m line 12
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return @"image/jpeg";
case 0x89:
return @"image/png";
case 0x47:
return @"image/gif";
case 0x49:
case 0x4D:
return @"image/tiff";
case 0x52:
// R as RIFF for WEBP
if ([data length] < 12) {
return nil;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return @"image/webp";
}
}
UIButton+WebCache - 为UIButton异步加载图片
UIImage+GIF / UIImage+MultiFormat / UIImage+WebP - 将image data转换成制定格式图片
// UIImage+MultiFormat.m line 25
UIImage *image;
NSString *imageContentType = [NSData sd_contentTypeForImageData:data];
if ([imageContentType isEqualToString:@"image/gif"]) {
image = [UIImage sd_animatedGIFWithData:data];
}
#ifdef SD_WEBP
else if ([imageContentType isEqualToString:@"image/webp"])
{
image = [UIImage sd_imageWithWebPData:data];
}
#endif
else {
image = [[UIImage alloc] initWithData:data];
UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
if (orientation != UIImageOrientationUp) {
image = [UIImage imageWithCGImage:image.CGImage
scale:image.scale
orientation:orientation];
}
}
UIImageView+WebCache / UIImageView+HighlightedWebCache - 为UIImageView异步加载图片
UIView+WebCacheOperation - 保存当前MKAnnotationView / UIButton / UIImageView异步下载图片的operations
Utils
- SDWebImageManager - 核心管理器, 主要就是对缓存管理 + 下载管理进行了封装, 其中最主要的接口就是
// SDWebImageManager.h line 212
- (id )downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
- SDWebImageDecoder - 图片解压缩, 其中只有一个接口
// SDWebImageDecoder.h line 16
+ (UIImage *)decodedImageWithImage:(UIImage *)image;
- SDWebImagePrefetcher - 低优先级情况下预先下载图片, 对SDWebImageManager进行简单的封装
// SDWebImagePrefetcher.m line 39
- (id)initWithImageManager:(SDWebImageManager *)manager {
if ((self = [super init])) {
_manager = manager;
_options = SDWebImageLowPriority;
_prefetcherQueue = dispatch_get_main_queue();
self.maxConcurrentDownloads = 3;
}
return self;
}
问题
SDScaledImageForKey的作用?
首先, 我们来看一下UIImage的size和scale是什么(定义引自Apple API Reference - UIImage)
size - The logical dimensions of the image, measured in points
scale - The scale factor of the image
当使用imageNamed加载图片时, iOS会根据当前设备scale以及图片后缀计算出size和scale
例如对于120*120pixel的图片来说, 在设备phone6(scale=2)上加载图片时
图片名没有@2x, @3x后缀, size = {60, 60}, scale = 2.0
图片名的后缀为@2x, size = {60, 60}, scale = 2.0
图片名的后缀为@3x, size = {40, 40}, scale = 3.0
而从网络下载的图片, size和pixel是一一对应的, 即1 size = 1 pixel
例如对于上述120*120pixel的图片来说
- size = {120, 120}, scale = 1.0
对于@2x, @3x的图片来说, 此时的size和scale是不正确的
所以SDScaledImageForKey就是将size和scale更新成与图片名后缀一致
decodedImageWithImage的作用?
decodedImageWithImage主要是对图片进行解压缩
当从Disk读取或从网络下载完图片后, 都会对图片进行解压缩
由于Disk读取或从网络下载都是在子线程中进行的, 所以解压缩操作不会占用主线程资源
这样当加载UIImage时, 主线程就省去了加压缩的操作
这里需要留意的是, 在decodedImageWithImage中使用了@autoreleasepool以降低内存瞬时峰值, 更多可以参考Understanding SDWebImage - Decompression
如何加载超大图片?
PS: 对于这个问题, 其实本人也没还遇到过, 但是在了解上述SDWebImage的机制后, 完全是有可能发生内存占用过高的情况(虽然SDWebImage告诉我们不要担心内存, 它会处理UIApplicationDidReceiveMemoryWarningNotification)
我们可以从以下三个方面进行优化
- Lazy Loading
这也是iOS中一个非常重要的原则, 小到通过getter实现成员变量的懒加载, 大到对象的创建
对于图片这种大量占用内存的资源来说, 我们更需要按需进行Lazy Loading
例如当在列表显示时, 我们可以加载缩略图, 在查看详情时, 才加载原来的大图, 当然这点优化可能需要服务器的配合, 即为缩略图和原图创建不同的URL
- 等比压缩
在对图片质量没有严格要求的情况下, 我们可以修改SDWebImage的源码, 在图片下载完成后, 对图片进行等比压缩
详细参考解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题
当然这种方法其实有一个值得商榷的地方, 如果客户端确定不显示原图, 那么服务器何不直接压缩和处理呢?
- 分块加载
android开发中加载大图, 也有分块加载的优化方式: BitmapRegionDecoder(详细参考android开发 之 优化篇)
iOS中也有分块加载图片的方法
CGImageRef CGImageCreateWithImageInRect(CGImageRef image, CGRect rect);
更多代码示例, 可以参考RSKImageCropper
参考
SDWebImage
How is SDWebImage better than X?
How to support webp?
Understanding SDWebImage - Decompression
SDWebImage实现分析
Mobile design 101: pixels, points and resolutions
更多文章, 请支持我的个人博客