UIButton+WebCache
UIImageView+HighlightedWebCache
UIImageView+WebCache
在源码中这些UIView子类的分类都会最终调用UIView+WebCache 中的全能初始化来进行设置图片。我看了一下UIButton+WebCache,理解为这样。
// 保存各类型图片各个状态state的key
static inline NSString * imageURLKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"image_%lu", (unsigned long)state];
}
static inline NSString * backgroundImageURLKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"backgroundImage_%lu", (unsigned long)state];
}
static inline NSString * imageOperationKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"UIButtonImageOperation%lu", (unsigned long)state];
}
static inline NSString * backgroundImageOperationKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state];
}
#pragma mark - Image
// 获取当前正常图片的url
- (nullable NSURL *)sd_currentImageURL {
// 从sd_imageURLStorage字典 中获取当前state为key对应的url
NSURL *url = self.sd_imageURLStorage[imageURLKeyForState(self.state)];
if (!url) {
// 没有,则获取state == UIControlStateNormal 时的url
url = self.sd_imageURLStorage[imageURLKeyForState(UIControlStateNormal)];
}
return url;
}
// 下列有七个接口,全能的初始化方法为最后一个,开发中自定义视图的接口设计尽量参照全能初始化来创建。
- (nullable NSURL *)sd_imageURLForState:(UIControlState)state {
return self.sd_imageURLStorage[imageURLKeyForState(state)];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
}
// 全能初始化方法
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock {
// 设置图片url
if (!url) {
[self.sd_imageURLStorage removeObjectForKey:imageURLKeyForState(state)];
} else {
self.sd_imageURLStorage[imageURLKeyForState(state)] = url;
}
// 调用UIView+WebCache 中的全能初始化方法设置图片
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:imageOperationKeyForState(state)
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setImage:image forState:state];
}
progress:nil
completed:completedBlock];
}
#pragma mark - Cancel
// 取消当前图片的下载
- (void)sd_cancelImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:imageOperationKeyForState(state)];
}
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:backgroundImageOperationKeyForState(state)];
}
#pragma mark - Private
// 给UIButton关联一个字典dic,保存image的url,key为图片设置的state
- (SDStateImageURLDictionary *)sd_imageURLStorage {
SDStateImageURLDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
if (!storage) {
// 如果为空,调用get
storage = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return storage;
}
UIView+WebCache
所有UIView以及子类都通过下面这个方法加载图片
/**
<#Description#>
@param url 图片的网址
@param placeholder 占位图,图像获取到后替换占位图
@param options 图片加载选项 是个位移枚举
@param operationKey 用作操作键的字符串。如果为空,将使用类名.主要使用来取消一个 operation
@param setImageBlock 设置图片回调
@param progressBlock 进度回调
@param completedBlock 图片加载完成回调
@param context <#context description#>
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context {
// key为当前UIView类的名称
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 先取消当前类的其他Operation对象
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
// 添加一个url属性
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 如果没有options 或者 不是延时显示占位符
if (!(options & SDWebImageDelayPlaceholder)) {
// 这个是用于FLAnimatedImageView+WebCache,FLAnimatedImageView是由Flipboard开源的iOS平台上播放GIF动画的一个优秀解决方案,在内存占用和播放体验都有不错的表现。这里主要用SDWebImage的缓存机制,制作一个简易使用的类别。
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
}
// 设置占位图
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
// 如果有url
if (url) {
// 获取图片(先从内存找,若无再从磁盘找,若仍无则从网络下载。)
// 先从内存找,若找到,则在主线程设置图片,并调用block回调;若在磁盘找到,则还要将其添加到内存缓存再做后续的;若是从网络下载而得,则内存和磁盘均要添加缓存,而后再进行后续的事。
// 如果需要显示菊花转
if ([self sd_showActivityIndicatorView]) {
// 添加菊花
[self sd_addActivityIndicator];
}
// 重置进度的总单元数
self.sd_imageProgress.totalUnitCount = 0;
// 当前完成的单元数
self.sd_imageProgress.completedUnitCount = 0;
// 获取Manager进行下载
SDWebImageManager *manager;
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
} else {
manager = [SDWebImageManager sharedManager];
}
// 下载进度block
__weak __typeof(self)wself = self;
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
wself.sd_imageProgress.totalUnitCount = expectedSize;
wself.sd_imageProgress.completedUnitCount = receivedSize;
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
// 开启队列下载
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
if (!sself) { return; }
// 完成后移除指示器
[sself sd_removeActivityIndicator];
// if the progress not been updated, mark it to complete state
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
// 是否不显示图片
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
// 如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
// 设置图片
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
}
// 如果设置了延时显示占位图,则显示placeholder
else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = sself.sd_imageTransition;
}
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
// 使用gcd的任务组来监听任务的完成,做block回调
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
});
// ensure completion block is called after custom setImage process finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
callCompletedBlockClojure();
});
} else {
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
callCompletedBlockClojure();
});
}
}];
//重新绑定operation到当前self
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
// 移除菊花,显示error为nil的错误。
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
网友评论