SDWebImage

Posted 驭狼共舞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SDWebImage相关的知识,希望对你有一定的参考价值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * The image wasn‘t available the SDWebImage caches, but was downloaded from the web.
     该图像是不可用的SDWebImage缓存,但是从网络下载的.
     */
    SDImageCacheTypeNone,
    /**
     * The image was obtained from the disk cache.
     图像从磁盘高速缓存获得.
     */
    SDImageCacheTypeDisk,
    /**
     * The image was obtained from the memory cache.
     图像从存储器高速缓存获得
     */
    SDImageCacheTypeMemory
};

SDWebImage是ios开发者经常使用的一个开源框架,这个框架的主要作用是:一个异步下载图片并且支持缓存的UIImageView分类。

UIImageView+WebCache

我们最常用的方法就是这个:

1
[_fineImageView sd_setImageWithURL:picURL placeholderImage:nil];

现在开始我们一步步地看这个方法的内部实现:

1
2
3
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

这里会调用下面这个方法:

1
2
3
4
- (void)sd_setImageWithURL:(NSURL *)url
          placeholderImage:(UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock

我们看UIImageView+WebCache.h文件,我们可以发现为开发者提供了很多类似于- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder的方法。

这些方法最终都会调用- sd_setImageWithURL: placeholderImage: options: progress: completed:,这个方法可以算是核心方法,下面我们看一下- sd_setImageWithURL: placeholderImage: options: progress: completed:

方法的实现:

首先执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//移除UIImageView当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行这一行代码,保证这个ImageView的下载和缓存组合操作都被取消
[self sd_cancelCurrentImageLoad];
接下来我们来看看[self sd_cancelCurrentImageLoad]内部是怎么执行的:
- (void)sd_cancelCurrentImageLoad {
    [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}
//然后会调用UIView+WebCacheOperation的
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key
UIView+WebCacheOperation
下面我们先来看看UIView+WebCacheOperation里面都写了些什么:
UIView+WebCacheOperation这个分类提供了三个方法,用于操作绑定关系
#import #import "SDWebImageManager.h"
@interface UIView (WebCacheOperation)
/**
 *  Set the image load operation (storage in a UIView based dictionary)
设置图像加载操作(存储在和UIView做绑定的字典里面)
 *
 *  @param operation the operation
 *  @param key       key for storing the operation
 */
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;
/**
 *  Cancel all operations for the current UIView and key
  用这个key找到当前UIView上面的所有操作并取消
 *
 *  @param key key for identifying the operations
 */
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;
/**
 *  Just remove the operations corresponding to the current UIView and key without cancelling them
 *
 *  @param key key for identifying the operations
 */
- (void)sd_removeImageLoadOperationWithKey:(NSString *)key;

为了方便管理和找到视图正在进行的一些操作,WebCacheOperation将每一个视图的实例和它正在进行的操作(下载和缓存的组合操作)绑定起来,实现操作和视图一一对应关系,以便可以随时拿到视图正在进行的操作,控制其取消等,如何进行绑定我们在下面分析:

UIView+WebCacheOperation.m文件内

- (NSMutableDictionary *)operationDictionary用到了中定义的两个函数:

  • objc_setAssociatedObject

  • objc_getAssociatedObject

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
-(void)setAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}

objc_setAssociatedObject作用是对已存在的类在扩展中添加自定义的属性,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的。

objc_getAssociatedObject根据key获得与对象绑定的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (NSMutableDictionary *)operationDictionary {
/*
这个loadOperationKey 的定义是:static char loadOperationKey;
它对应的绑定在UIView的属性是operationDictionary(NSMutableDictionary类型)
operationDictionary的value是操作,key是针对不同类型视图和不同类型的操作设定的字符串
注意:&是一元运算符结果是右操作对象的地址(&loadOperationKey返回static char loadOperationKey的地址)
*/
    NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//如果可以查到operations,就rerun,反正给视图绑定一个新的,空的operations字典
    if (operations) {
        return operations;
    }
    operations = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    // 取消正在下载的队列
    NSMutableDictionary *operationDictionary = [self operationDictionary];
//如果 operationDictionary可以取到,根据key可以得到与视图相关的操作,取消他们,并根据key值,从operationDictionary里面删除这些操作
    id operations = [operationDictionary objectForKey:key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id  operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

接下来我们继续探索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- sd_setImageWithURL: placeholderImage: options: progress: completed:
- (void)sd_setImageWithURL:(NSURL *)url
          placeholderImage:(UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_cancelCurrentImageLoad];
    //将 url作为属性绑定到ImageView上,用static char imageURLKey作key
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/*options & SDWebImageDelayPlaceholder这是一个位运算的与操作,!(options & SDWebImageDelayPlaceholder)的意思就是options参数不是SDWebImageDelayPlaceholder,就执行以下操作
#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
*/

这是一个宏定义,因为图像的绘制只能在主线程完成,所以dispatch_main_sync_safe就是为了保证block在主线程中执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
//设置imageView的placeHolder
            self.image = placeholder;
        });
    }
    if (url) {
        // 检查是否通过`setShowActivityIndicatorView:`方法设置了显示正在加载指示器。如果设置了,使用`addActivityIndicator`方法向self添加指示器
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }
        __weak __typeof(self)wself = self;
//下载的核心方法
        id  operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options
progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
          //移除加载指示器
            [wself removeActivityIndicator];
          //如果imageView不存在了就return停止操作
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
/*
SDWebImageAvoidAutoSetImage,默认情况下图片会在下载完毕后自动添加给imageView,但是有些时候我们想在设置图片之前加一些图片的处理,就要下载成功后去手动设置图片了,不会执行`wself.image = image;`,而是直接执行完成回调,有用户自己决定如何处理。
*/
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
/*
如果后两个条件中至少有一个不满足,那么就直接将image赋给当前的imageView
,并调用setNeedsLayout
*/
                else if (image) {
                    wself.image = image;
                    [wself setNeedsLayout];
                else {
/*
image为空,并且设置了延迟设置占位图,会将占位图设置为最终的image,,并将其标记为需要重新布局。
  */
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
 // 为UIImageView绑定新的操作,以为之前把ImageView的操作cancel了
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    else {
 // 判断url不存在,移除加载指示器,执行完成回调,传递错误信息。
        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);
            }
        });
    }
}

SDWebImageManager

-sd_setImageWithURL:forState:placeholderImage:options:completed:中,下载图片方法是位于SDWebImageManager类中- downloadImageWithURL: options:progress:completed:函数

我们看下文档是对SDWebImageManager怎么描述的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
 * It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
 * You can use this class directly to benefit from web image downloading with caching in another context than
 * a UIView.
 *
 * Here is a simple example of how to use SDWebImageManager:
 *
 * @code
/*
概述了SDWenImageManager的作用,其实UIImageVIew+WebCache这个Category背后执行操作的就是这个SDWebImageManager.它会绑定一个下载器也就是SDwebImageDownloader和一个缓存SDImageCache
*/
/**
 * Downloads the image at the given URL if not present in cache or return the cached version otherwise.
   若图片不在cache中,就根据给定的URL下载图片,否则返回cache中的图片
 *
 * @param url            The URL to the image
 * @param options        A mask to specify options to use for this request
 * @param progressBlock  A block called while image is downloading
 * @param completedBlock A block called when operation has been completed.
 *
 *   This parameter is required.
 *
 *   This block has no return value and takes the requested UIImage as first parameter.
 *   In case of error the image parameter is nil and the second parameter may contain an NSError.
 *
 *   The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
 *   or from the memory cache or from the network.
 *
 *   The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is
 *   downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
 *   block is called a last time with the full image and the last parameter set to YES.
 *
 * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
 */
/*
*第一个参数是必须的,就是image的url
*第二个参数options你可以定制化各种各样的操作
*第三个参数是一个回调block,用于图片下载过程中的回调
*第四个参数是一个下载完成的回调,会在图片下载完成后回调
*返回值是一个NSObject类,并且这个NSObject类是遵循一个协议这个协议叫SDWebImageOperation,这个协议里面只写了一个协议,就是一个cancel一个operation的协议
*/
- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

我们继续看SDWebImageManager .m

  • 初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
*初始化方法
*1.获得一个SDImageCache的单例
*2.获得一个SDWebImageDownloader的单例
*3.新建一个MutableSet来存储下载失败的url
*4.新建一个用来存储下载operation的可遍数组
*/
- (id)init {
    if ((self = [super init])) {
        _imageCache = [self createCache];
        _imageDownloader = [SDWebImageDownloader sharedDownloader];
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}

利用image的url生成一个缓存时需要的key,cacheKeyFilter的定义如下:

1
2
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

是一个可以返回一个字符串的block

1
2
3
4
5
6
7
8
9
10
//如果检测到cacheKeyFilter不为空的时候,利用cacheKeyFilter来生成一个key
//如果为空,那么直接返回URL的string内容,当做key.
- (NSString *)cacheKeyForURL:(NSURL *)url {
    if (self.cacheKeyFilter) {

(c)2006-2024 SYSTEM All Rights Reserved IT常识