iOS 开发继续加油

SDWebImage - Category

2019-06-13  本文已影响41人  二斤寂寞

各种分类:以UIView+WebCache为例分析

UIImageViewUIButtonFLAnimatedImageView都会调用UIView(WebCache)分类的sd_internalSetImageWithURL方法来做图片加载请求。具体是通过SDWebImageManager调用来实现的。同时实现了Operation取消ActivityIndicator的添加与取消。我们首先来看 sd_internalSetImageWithURL方法的实现:

/**
 所有UIView及其子类都是通过这个方法来加载图片

 @param url 加载的url
 @param placeholder 占位图
 @param options 加载选项
 @param operationKey key
 @param setImageBlock Block
 @param progressBlock 进度Block
 @param completedBlock 回调Block
 */
- (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 {
    
    //取消当前类所对应的所有下载Operation对象
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    /*
     把UIImageView的加载图片操作和他自身用关联对象关联起来,方便后面取消等操作。关联的key就是UIImageView对应的类名
     */
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //如果有设置站位图,则先显示站位图
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    
    if (url) {
        // check if activityView is enabled or not
        //如果UIImageView对象有设置添加转动菊花数据,加载的时候添加转动的菊花
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
        
        __weak __typeof(self)wself = self;
        /*
         *operation是一个`SDWebImageCombinedOperation`对象。通过这个对象来获取图片
         */
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            //停止菊花
            [sself sd_removeActivityIndicator];
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                //如果设置了不自动显示图片,则直接调用completedBlock,让调用者处理图片的显示
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    //自动显示图片
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                } else {
                    //如果设置了延迟显示占位图,则图片加载失败的情况下显示占位图
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                //完成回调
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        //关联Operationkey与Operation对象。方便后面根据key取消operation操作等。
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
        //加载失败的情况
        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);
            }
        });
    }
}

给UIView及其子类添加旋转菊花是通过关联对象来实现的。通过如下几个方法来实现:

#pragma mark 通过关联对象来实现菊花的添加
- (UIActivityIndicatorView *)activityIndicator {
    return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}

- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
    objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
#pragma mark 是否显示旋转菊花
- (void)sd_setShowActivityIndicatorView:(BOOL)show {
    objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
}

- (BOOL)sd_showActivityIndicatorView {
    return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
#pragma mark 旋转菊花的样式
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
    objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}

- (int)sd_getIndicatorStyle{
    return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}

还有就是通过UIView+WebCacheOperation类来实现UIView的图片下载Operation的关联和取消。具体key的值可以从sd_internalSetImageWithURL中找到具体获取方式,通过在这个方法中实现 Operation的关联与取消。

/**
 关联Operation对象与key对象

 @param operation Operation对象
 @param key key
 */
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
    if (key) {
        [self sd_cancelImageLoadOperationWithKey:key];
        if (operation) {
            SDOperationsDictionary *operationDictionary = [self operationDictionary];
            operationDictionary[key] = operation;
        }
    }
}
/**
 取消当前key对应的所有实现了SDWebImageOperation协议的Operation对象

 @param key Operation对应的key
 */
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    //获取当前View对应的所有key
    SDOperationsDictionary *operationDictionary = [self operationDictionary];
    //获取对应的图片加载Operation
    id operations = operationDictionary[key];
    //取消所有当前View对应的所有Operation
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}
上一篇下一篇

猜你喜欢

热点阅读