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