【iOS】 SDWebImage | 快速入门、缓存定制

6,008 阅读5分钟

SDWebImage 是一个强大的网络图像异步缓存框架,使用非常方便。 本章介绍 SDWebImage 的基本用法以及如何缓存处理后的图片。

一、快速接入

目前SDWebImage最新的版本为5.8.4你可以通过Cocoapods集成:

pod 'SDWebImage', '~> 5.8.4'

二、基础用法

SDWebImage优先从内存读取缓存,无缓存则从磁盘查找,查找失败直接从网络下载。 内存数据的读取速度是非常快的,通常网络下载或者磁盘读取的图片数据会被缓存到内存,如果缓存达到阈值,缓存算法会将内存中低频使用的图片缓存移除,优秀的缓存算法将有助于改进你的App性能。

SDWebImage 提供了对UIImageViewUIButton的扩展,你可以在UIImageView+WebCacheUIButton+WebCache 文件中查看具体接口和方法实现。

如果非常熟悉SDWebImage框架,可忽略此部分

使用前先导入头文件

    #import<SDWebImage/UIImageView+WebCache.h>

2.1 URL异步加载

传入图片链接即可完成图片缓存,内部做了防止重复下载的设计。

[self.imageView sd_setImageWithURL:[NSURL urlWithString:urlString]];

2.2 设置占位图

占位图会优先显示到UIImageView上,图片加载成功后会替换占位图,占位图的显示时长与第一次图片下载时长有关,如果图片加载很快,你可以看到占位图一闪而过,当图片被缓存后,你几乎看不到占位图显示。

[self.imageView sd_setImageWithURL:[NSURL urlWithString:urlString] 
                  placeholderImage:[UIImage imageNamed:@"myPlaceHolder"]];

2.3 添加完成回调

需要知道图片下载结果并做一些处理,配置一下代理回调block即可:

[self.imageView sd_setImageWithURL:[NSURL urlWithString:urlString] 
                  placeholderImage:[UIImage imageNamed:@"myPlaceHolder"]
                  completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                      if (error) {
                      }
                      else {
                          
                      }
                  }];

2.4 添加下载进度

你可以设置process block代理回调来获取下载进度反馈给用户

[self.imageView sd_setImageWithURL:[NSURL urlWithString:urlString] 
                  placeholderImage:[UIImage imageNamed:@"myPlaceHolder"]
                  options:SDWebImageRetryFailed | SDWebImageFromCacheOnly
                  progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * targetURL){
                      // 更新UI下载进度
                  }
                  completed:nil];

2.5 Options

SDWebImageOptions提供丰富的选项来满足你的缓存需求,具体看SDWebImageOptions枚举说明。

/// WebCache options
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    /// 失败重试
    SDWebImageRetryFailed = 1 << 0,
    /// 低优先级,交互的时候延时下载
    SDWebImageLowPriority = 1 << 1,
    /// 渐进式下载,图像在下载过程中会像浏览器一样逐步显示。
    SDWebImageProgressiveLoad = 1 << 2,
   	/// 刷新缓存
    SDWebImageRefreshCached = 1 << 3,
    /// 支持背后下载
    SDWebImageContinueInBackground = 1 << 4,
    /// 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES处理存储在NSHTTPCookieStore中的cookie
    SDWebImageHandleCookies = 1 << 5,
    /// 启用以允许不受信任的SSL证书。在生产中请谨慎使用
    SDWebImageAllowInvalidSSLCertificates = 1 << 6,
    /// 默认情况下,图像按排队顺序加载。 此标志将它们移至队列的最前面。
    SDWebImageHighPriority = 1 << 7,
    /// 默认情况下,占位符图像在加载图像时加载。 该标志将延迟加载占位符图像直到图像加载完成为止。
    SDWebImageDelayPlaceholder = 1 << 8,
    /// 对动画图像应用变换
    SDWebImageTransformAnimatedImage = 1 << 9,
    /// 下载完成不设置图像到ImageView中
    SDWebImageAvoidAutoSetImage = 1 << 10,
    /// 此标志会将图像缩小到与设备的受限内存兼容的大小。
    SDWebImageScaleDownLargeImages = 1 << 11,
    /// 强制查询内中存图像数据
    SDWebImageQueryMemoryData = 1 << 12,
    /// 同步查询内存数据
    SDWebImageQueryMemoryDataSync = 1 << 13,
    /// 同步查询磁盘数据
    SDWebImageQueryDiskDataSync = 1 << 14,
    /// 仅从内存读取图像
    SDWebImageFromCacheOnly = 1 << 15,
    /// 仅从loader加载
    SDWebImageFromLoaderOnly = 1 << 16,
    /// 强制将视图转换应用于内存和磁盘缓存。
    SDWebImageForceTransition = 1 << 17,
    /// 可以防止对图像进行解码,以避免过多的内存消耗
    SDWebImageAvoidDecodeImage = 1 << 18,
    /// 强制解码第一帧并生成静态图像。
    SDWebImageDecodeFirstFrameOnly = 1 << 19,
    /// 将所有帧预加载到内存中,以减少CPU使用率。(仅限磁盘缓存和下载)
    SDWebImagePreloadAllFrames = 1 << 20,
    /// 使用此选项,可以确保我们始终使用您提供的类回调图像。
    SDWebImageMatchAnimatedImageClass = 1 << 21,
    /// 如果需要在完成块中处理磁盘缓存,则应使用此选项以确保在回调时已写入磁盘缓存。
    SDWebImageWaitStoreCache = 1 << 22,
    /// 对矢量图像应用变换
    SDWebImageTransformVectorImage = 1 << 23,
};

三、高级扩展

3.1 如何使用

如果需要对原始图像处理后再缓存,无需调用SDWebImageManager进行复杂的封装,依然可以使用UIImageView分类接口处理。 下面是完整参数接口:

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

其中context参数是这样描述的

@param context        A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.

先看下SDWebImageContext的定义

typedef NSString * SDWebImageContextOption NS_EXTENSIBLE_STRING_ENUM;
typedef NSDictionary<SDWebImageContextOption, id> SDWebImageContext;

SDWebImageContext是一个字典类型,key值可设置SDWebImageContextOption定义字符串。 SDWebImageContextOption有两组重要定义如下:

/**
 A id<SDImageTransformer> instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. (id<SDImageTransformer>)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageTransformer;

/**
 A CGFloat raw value which specify the image scale factor. The number should be greater than or equal to 1.0. If not provide or the number is invalid, we will use the cache key to specify the scale factor. (NSNumber)
 */
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageScaleFactor;

通过设置SDWebImageContext的上述SDWebImageContextOption值可以对图片转换和缩放等。其中SDWebImageContextImageTransformer对应的value需要实现SDImageTransformer协议。

3.2 SDImageTransformer协议

SDWebImage定义了SDImageTransformer协议,通过此协议可以实现图片的transform处理,处理后的图片被缓存,原始图像被丢弃。

协议实现如下:

@protocol SDImageTransformer <NSObject>
@required
/**
 For each transformer, it must contains its cache key to used to store the image cache or query from the cache. This key will be appened after the original cache key generated by URL or from user.
 @return The cache key to appended after the original cache key. Should not be nil.
 */
@property (nonatomic, copy, readonly, nonnull) NSString *transformerKey;
- (nullable UIImage *)transformedImageWithImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key;
@end

transformerKey 是缓存图片的唯一标记,SDWebImage通过url+transformerKey组合键实现对缓存图片的查找。

注意

如果需要支持动态图像处理,请设置SDWebImageOptions为以下参数

/**
 * 我们通常不对动画图像应用变换,因为大多数transformer无法管理动画图像。
 * 始终使用此标志对其进行转换。
 */
 SDWebImageTransformAnimatedImage = 1 << 9,

3.3 默认 Transformer

SDWebImage提供了几种简单的Transformer能力,本节我们将对以下图像做处理: 原始图像

3.3.1 RoundCorner

给图片添加圆角和边框

@interface SDImageRoundCornerTransformer: NSObject <SDImageTransformer>
@property (nonatomic, assign, readonly) CGFloat cornerRadius;
@property (nonatomic, assign, readonly) SDRectCorner corners;
@property (nonatomic, assign, readonly) CGFloat borderWidth;
@property (nonatomic, strong, readonly, nullable) UIColor *borderColor;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor;
@end

全部圆角+边框

UIColor *color =  [UIColor qmui_colorWithHexString:@"#FF4275"];
SDImageRoundCornerTransformer *transformer = [SDImageRoundCornerTransformer transformerWithRadius:1000 corners:UIRectCornerAllCorners borderWidth:5.0 borderColor:color];

效果如下:

3.3.2 Resizing

实现图片缩放,通常一些服务器提供了大、中、小等图片链接,如未做处理且返回的图片过大可以调用此类进行图片缩放。

@interface SDImageResizingTransformer : NSObject <SDImageTransformer>
/// 要调整的size,值应为正。
@property (nonatomic, assign, readonly) CGSize size;
@property (nonatomic, assign, readonly) SDImageScaleMode scaleMode;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode;
@end

缩放到Size(100,44)

3.3.3 Cropping

轻松裁剪你想要的图片

@interface SDImageCroppingTransformer : NSObject <SDImageTransformer>
@property (nonatomic, assign, readonly) CGRect rect;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithRect:(CGRect)rect;
@end

裁剪出右边区域 Rect(540, 0, 585, 492)

3.3.4 Flipping

水平、垂直翻转

@interface SDImageFlippingTransformer : NSObject <SDImageTransformer>
@property (nonatomic, assign, readonly) BOOL horizontal;
@property (nonatomic, assign, readonly) BOOL vertical;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical;
@end

水平和垂直翻转

3.3.5 Rotation

方向旋转

@interface SDImageRotationTransformer : NSObject <SDImageTransformer>
/**
 Rotated radians in counterclockwise.⟲
 */
@property (nonatomic, assign, readonly) CGFloat angle;
/**
 YES: new image's size is extend to fit all content.
 NO: image's size will not change, content may be clipped.
 */
@property (nonatomic, assign, readonly) BOOL fitSize;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize;
@end

旋转130°

3.3.6 Tint color

添加tintColor

@interface SDImageTintTransformer : NSObject <SDImageTransformer>
@property (nonatomic, strong, readonly, nonnull) UIColor *tintColor;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithColor:(nonnull UIColor *)tintColor;
@end

3.3.7 Blur

实现模糊效果

@interface SDImageBlurTransformer : NSObject <SDImageTransformer>
/**
 The radius of the blur in points, 0 means no blur effect.
 */
@property (nonatomic, assign, readonly) CGFloat blurRadius;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithRadius:(CGFloat)blurRadius;
@end

blur半径10

3.3.8 Filter

实现滤镜操作

@interface SDImageFilterTransformer: NSObject <SDImageTransformer>
@property (nonatomic, strong, readonly, nonnull) CIFilter *filter;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithFilter:(nonnull CIFilter *)filter;
@end

黑白滤镜CIMinimumComponent

3.3.9 Pipeline

叠加处理

/**
 你可以绑定多个transformer对图像按顺序一一处理并生成最终图像
 */
@interface SDImagePipelineTransformer : NSObject <SDImageTransformer>
@property (nonatomic, copy, readonly, nonnull) NSArray<id<SDImageTransformer>> *transformers;
- (nonnull instancetype)init NS_UNAVAILABLE;
+ (nonnull instancetype)transformerWithTransformers:(nonnull NSArray<id<SDImageTransformer>> *)transformers;
@end

圆角、裁剪、翻转、滤镜等合并:

四、快速实践

通过SDImageTransformer协议简单实现一个图片缩放类:

/// 头文件实现
@interface SSImageResizingTransformer : NSObject <SDImageTransformer>
+ (instancetype)transformerWithSize:(CGSize)size scaleMode:(QMUIImageResizingMode)scaleMode;
@end
/// .m文件实现
@interface SSImageResizingTransformer ()
@property (nonatomic, assign) CGSize size;
@property (nonatomic, assign) SDImageScaleMode scaleMode;
@end

@implementation SSImageResizingTransformer
+ (instancetype)transformerWithSize:(CGSize)size scaleMode:(QMUIImageResizingMode)scaleMode {
    SSImageResizingTransformer *transformer = [SSImageResizingTransformer new];
    transformer.size = size;
    transformer.scaleMode = scaleMode;
    return transformer;
}
/// 缓存key,通过缩放模式、宽度、高度对图片进行标记
- (NSString *)transformerKey {
    CGSize size = self.size;
    return [NSString stringWithFormat:@"SSImageResizingTransformer({%f,%f},%lu)", size.width, size.height, (unsigned long)self.scaleMode];
}
/// 协议缩放实现
- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key {
    if (!image) {
        return nil;
    }
    if (self.size.width <= 0 || self.size.height <= 0) {
        return image;
    }
    if (image.size.width < self.size.width && image.size.height < self.size.height){
        return image;
    }
    UIImage *scaledImage = [image qmui_imageResizedInLimitedSize:self.size resizingMode:QMUIImageResizingModeScaleAspectFit scale:UIScreen.mainScreen.scale];
    return scaledImage;
}

自定义协议很简单。配置唯一的缓存transformerKey,并在transformedImageWithImage:forKey:方法中实现详细逻辑即可,SDWebImage会自动缓存处理结果。

附上 SDWebImageDemo 代码,Demo包含了UIImageView图像定制分类接口。

五、小节

SDWebImage有很多丰富的功能待挖掘。本章通过SDImageTransformer协议虽然实现了对图片的灵活处理,但仍有个缺陷需要修复:同一个图片 URL 多处调用且样式和规则都不同,要生成不同缓存,原图需要重复下载,浪费流量,目前有两种解决方案:

1、一次性配置好所有缓存规格,在第一次图片下载后生成所有规格的图像。

2、对原始图片做缓存,后续直接拿缓存的原始图片处理生成指定规格的缓存

第一种方案不用缓存原图,但处理麻烦,用户不一定需要的资源也会被缓存,而且额外挤占硬件资源,第二种方案只需要修改SDWebImage部分代码逻辑实现原图缓存即可,后续将会补充如何修改此段逻辑。