SDWebImage源码阅读(十四)UIView+WebCache

  这是 SDWebImage 针对 UIView 添加的一个 WebCache 的分类。

 .h

 命名block

1 typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);

 定义的 Method

1 /**
2  * Get the current image URL.
3  *
4  * Note that because of the limitations of categories this property can get out of sync
5  * if you use setImage: directly.
6  */
7 - (nullable NSURL *)sd_imageURL;

  获取当前图像的 URL。

  注意:因为分类的限制,此属性可能不同步。例如直接使用 setImage: 。

 1 /**
 2  * Set the imageView `image` with an `url` and optionally a placeholder image.
 3  *
 4  * The download is asynchronous and cached.
 5  *
 6  * @param url            The url for the image.
 7  * @param placeholder    The image to be set initially, until the image request finishes.
 8  * @param options        The options to use when downloading the image. @see SDWebImageOptions for the possible values.
 9  * @param operationKey   A string to be used as the operation key. If nil, will use the class name
10  * @param setImageBlock  Block used for custom set image code
11  * @param progressBlock  A block called while image is downloading
12  *                       @note the progress block is executed on a background queue
13  * @param completedBlock A block called when operation has been completed. This block has no return value
14  *                       and takes the requested UIImage as first parameter. In case of error the image parameter
15  *                       is nil and the second parameter may contain an NSError. The third parameter is a Boolean
16  *                       indicating if the image was retrieved from the local cache or from the network.
17  *                       The fourth parameter is the original image url.
18  */
19 - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
20                   placeholderImage:(nullable UIImage *)placeholder
21                            options:(SDWebImageOptions)options
22                       operationKey:(nullable NSString *)operationKey
23                      setImageBlock:(nullable SDSetImageBlock)setImageBlock
24                           progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
25                          completed:(nullable SDExternalCompletionBlock)completedBlock;

  使用一个 url 和任意一个占位图片给 imageView 设置 image。

  这图片的下载是异步和有缓存的。

  NSURL *url 图片的URL。

  UIImage *placeholder 占位图片。

  SDWebImageOptions options 下载选项。值是 SDWebImageManager 里面定义的 SDWebImageOptions 枚举。

  NSString *operationKey 用作操作键的字符串。如果是 nil,将使用类名。

  SDSetImageBlock setImageBlock 该 block 用于自定义设置 image 的代码。

  SDWebImageDownloaderProgressBlock progressBlock 下载进度执行的 block,进度的 block 是在后台队列中执行。

  SDExternalCompletionBlock completedBlock completedBlock 图片下载完成时执行的 block。这个block 有四个参数,第一个参数是下载返回的图片,第二个参数是一个 NSError 类型,正常下载成功时是 nil,下载错误时是 NSError 错误信息,第三个参数是图片的缓存类型(磁盘、内存、无),第四个参数是下载图像的 url。

1 /**
2  * Cancel the current download
3  */
4 - (void)sd_cancelCurrentImageLoad;

  取消当前的下载。

 Activity indicator

1 /**
2  *  Show activity UIActivityIndicatorView
3  */
4 - (void)sd_setShowActivityIndicatorView:(BOOL)show;

  显示活动指示器。

1 /**
2  *  set desired UIActivityIndicatorViewStyle
3  *
4  *  @param style The style of the UIActivityIndicatorView
5  */
6 - (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;

  设置活动指示器的类型。

1 typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
2     UIActivityIndicatorViewStyleWhiteLarge,
3     UIActivityIndicatorViewStyleWhite,
4     UIActivityIndicatorViewStyleGray __TVOS_PROHIBITED,
5 }; 
1 - (BOOL)sd_showActivityIndicatorView;
2 - (void)sd_addActivityIndicator;
3 - (void)sd_removeActivityIndicator;

  获取活动指示器状态。

  添加活动指示器。

  移除活动指示器。

 .m

1 static char imageURLKey;
2 
3 #if SD_UIKIT
4 static char TAG_ACTIVITY_INDICATOR;
5 static char TAG_ACTIVITY_STYLE;
6 #endif
7 static char TAG_ACTIVITY_SHOW;

  定义了几个静态 char 变量,只是看名字大概能猜到它们分别所代表的含义。imageURLKey 主要用作给 UIView 对象关联对象时的关键字使用。

1 - (nullable NSURL *)sd_imageURL {
2     return objc_getAssociatedObject(self, &imageURLKey);
3 }

  给当前的 UIView 对象关联一个关键字是 imageURLKey 的对象。该方法是获取 imageURLKey 这个关键字所对应的关联对象。

 1 - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
 2                   placeholderImage:(nullable UIImage *)placeholder
 3                            options:(SDWebImageOptions)options
 4                       operationKey:(nullable NSString *)operationKey
 5                      setImageBlock:(nullable SDSetImageBlock)setImageBlock
 6                           progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
 7                          completed:(nullable SDExternalCompletionBlock)completedBlock {
 8     NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
 9     [self sd_cancelImageLoadOperationWithKey:validOperationKey];
10     objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
11     
12     if (!(options & SDWebImageDelayPlaceholder)) {
13         dispatch_main_async_safe(^{
14             [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
15         });
16     }
17     
18     if (url) {
19         // check if activityView is enabled or not
20         if ([self sd_showActivityIndicatorView]) {
21             [self sd_addActivityIndicator];
22         }
23         
24         __weak __typeof(self)wself = self;
25         id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
26             __strong __typeof (wself) sself = wself;
27             [sself sd_removeActivityIndicator];
28             if (!sself) {
29                 return;
30             }
31             dispatch_main_async_safe(^{
32                 if (!sself) {
33                     return;
34                 }
35                 if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
36                     completedBlock(image, error, cacheType, url);
37                     return;
38                 } else if (image) {
39                     [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
40                     [sself sd_setNeedsLayout];
41                 } else {
42                     if ((options & SDWebImageDelayPlaceholder)) {
43                         [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
44                         [sself sd_setNeedsLayout];
45                     }
46                 }
47                 if (completedBlock && finished) {
48                     completedBlock(image, error, cacheType, url);
49                 }
50             });
51         }];
52         [self sd_setImageLoadOperation:operation forKey:validOperationKey];
53     } else {
54         dispatch_main_async_safe(^{
55             [self sd_removeActivityIndicator];
56             if (completedBlock) {
57                 NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
58                 completedBlock(nil, error, SDImageCacheTypeNone, url);
59             }
60         });
61     }
62 }

  这个方法比较长,现在一行一行来看:

  每一个 UIView 对象都给它绑定一个 operationKey,通过这个 key 获取这个 operation。

  如果 operationKey 是 nil,就采用它自身的类名字符串作为 key。

  取消之前绑定的 operation。

  为自身关联一个关键字是 imageURLKey 的 NSURL 对象。

  如果下载选项不是 SDWebImageDelayPlaceholder(延迟显示占位图),调取主线程给当前 UIView 设置一张占位图片。

  setImageBlock 这个 block 会在view 上设置图片完毕的时候执行。

  如果 url 存在:

  检查 activityView 是否启用。

  如果可用就添加 ActivityIndicator。

  下面就开始下载图片:

  在下载完成的 block 里面,首先移除 ActivityIndicator。

  如果 slef 不存在,直接 return。

  调取主线程:

  如果 image 存在并且 options 是 SDWebImageAvoidAutoSetImage 并且 completedBlock 存在,则直接执行 completedBlock,不直接把 image 添加到 view 上。

  如果图像存在的话,下载选项不是 SDWebImageAvoidAutoSetImage 的时候,直接把下载的图片添加到 view 上。

  并调用:

1 - (void)setNeedsLayout;

  然后直接 return。  

  其它的情况,如果下载选项是 SDWebImageDelayPlaceholder (延迟显示占位图片),则在图像下载完毕的时候把占位图片添加到 view 上。并调用 setNeedsLayout。

   然后针对后面的两种情况,统一执行 completedBlock。

  给 UIView 绑定 operation。

  如果 url 不存在:

  在主线程移除 UIView 的所有 operation。

1 [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]

  然后 completedBlock 携带 NSError 错误信息执行。

1 - (void)sd_cancelCurrentImageLoad {
2     [self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
3 }

  取消 UIView 的所有操作。

 1 - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
 2     if (setImageBlock) {
 3         setImageBlock(image, imageData);
 4         return;
 5     }
 6     
 7 #if SD_UIKIT || SD_MAC
 8     if ([self isKindOfClass:[UIImageView class]]) {
 9         UIImageView *imageView = (UIImageView *)self;
10         imageView.image = image;
11     }
12 #endif
13     
14 #if SD_UIKIT
15     if ([self isKindOfClass:[UIButton class]]) {
16         UIButton *button = (UIButton *)self;
17         [button setImage:image forState:UIControlStateNormal];
18     }
19 #endif
20 }

  这个方法的是往 view 上添加图片。

  首先如果 setImageBlock 存在则执行 setImageBlock。

  并且直接 return。

  如果 setImageBlock 是 nil 则执行下面的操作:

  我们知道 SDWebImage 主要是用来给 UIImageView 和 UIButton 添加图片,而它们都是 UIView 的子类,所以这里的 UIView 的下载图片都是针对的它的子类来的。

  如果当前是 UIImageView 的话则给它的 image 属性赋值。

  如果当前是 UIButton 的话则用:

1 - (void)setImage:(nullable UIImage *)image forState:(UIControlState)state;                      // default is nil. should be same size if different for different states

  给 UIbutton 赋图。

1 - (void)sd_setNeedsLayout {
2 #if SD_UIKIT
3     [self setNeedsLayout];
4 #elif SD_MAC
5     [self setNeedsLayout:YES];
6 #endif
7 }

  针对 iOS 平台、TV 平台、MAC 平台,调用不同的方法。

  Activity indicator

1 #if SD_UIKIT
2 - (UIActivityIndicatorView *)activityIndicator {
3     return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
4 }
5 
6 - (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
7     objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
8 }
9 #endif

  关联对象的创建和获取。

  TAG_ACTIVITY_INDICATOR 做关键字 activityIndicator (UIActivityIndicatorView)做关联对象。

1 - (void)sd_setShowActivityIndicatorView:(BOOL)show {
2     objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
3 }
4 
5 - (BOOL)sd_showActivityIndicatorView {
6     return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
7 }

  是否显示活动指示器关联对象的创建和获取。

  TAG_ACTIVITY_SHOW 做关键字 show(Bool)做关联对象。

1 #if SD_UIKIT
2 - (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
3     objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
4 }
5 
6 - (int)sd_getIndicatorStyle{
7     return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
8 }
9 #endif

  活动指示器的类型关联对象的创建和获取。

  TAG_ACTIVITY_STYLE 做关键字 style(UIActivityIndicatorViewStyle)做关联对象。

 1 - (void)sd_addActivityIndicator {
 2 #if SD_UIKIT
 3     if (!self.activityIndicator) {
 4         self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
 5         self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
 6         
 7         dispatch_main_async_safe(^{
 8             [self addSubview:self.activityIndicator];
 9             
10             [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
11                                                              attribute:NSLayoutAttributeCenterX
12                                                              relatedBy:NSLayoutRelationEqual
13                                                                 toItem:self
14                                                              attribute:NSLayoutAttributeCenterX
15                                                             multiplier:1.0
16                                                               constant:0.0]];
17             [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
18                                                              attribute:NSLayoutAttributeCenterY
19                                                              relatedBy:NSLayoutRelationEqual
20                                                                 toItem:self
21                                                              attribute:NSLayoutAttributeCenterY
22                                                             multiplier:1.0
23                                                               constant:0.0]];
24         });
25     }
26     
27     dispatch_main_async_safe(^{
28         [self.activityIndicator startAnimating];
29     });
30 #endif
31 }

  给 UIView 添加活动指示器,并设置它的约束在 UIView 的正中心。

1 - (void)sd_removeActivityIndicator {
2 #if SD_UIKIT
3     if (self.activityIndicator) {
4         [self.activityIndicator removeFromSuperview];
5         self.activityIndicator = nil;
6     }
7 #endif
8 }  

  移除活动指示器。

 

参考链接:http://www.jianshu.com/p/8b4b9da69aa1

END

 

posted @ 2017-06-10 15:19  鳄鱼不怕牙医不怕  阅读(279)  评论(0编辑  收藏  举报