iOS----------SDWebimage源码解析(1)

本文深入解析了SDWebimage的源码结构,特别是从UIImageView到SDWebImageManager的核心部分。详细介绍了如何通过UIImageView+WebCache类实现网络图片加载与缓存管理,包括如何设置图片、取消加载以及使用多线程优化图片下载过程。同时,通过代码分析,揭示了SDWebImageManager在图片加载过程中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上个月通过观看其他人的博客,查阅网络网络资料,简单写了一些SDWebimage源码的解析,当我看完AFNetworking觉得应该再重新在按照学习AFN的模式学习下SDWebimage,下面我们开始对SDWebimage的学习。
首先看下文件目录这里写图片描述
在这个文件目录中我们发现最多的是带“+”的类名,很显然他们都是在其基础上添加的category,然后里面有cache这个是跟缓存有关的吧,operation结尾的 只有.h文件,是SDWebimage中的多线程的标示,manager大多数的框架的核心代码救灾这个manager中。
我们先从最熟悉的UIImageView 开始,然后逐步过渡到manager类
1、UIImageView+WebCache
在该类的.h文件中,给imageView添加图片的方法

- (void)sd_setImageWithURL:(NSURL *)url;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;

- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;

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

- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

以上这些方法最根本的都会调用到一个私有方法- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock后面在.m文件中会重点介绍这个方法。

下面我们来看看核心代码的实现

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

    //将原来的operation停止
    [self sd_cancelCurrentImageLoad];

    //给UIImageView关联要下载的图片的url属性
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    //options:该请求所要遵循的选项
    //在还没发送请求获取网络端图片之前(即网络端的image还没加载),如果options中有SDWebImageDelayPlaceholder这一选项,就不给image赋值,如果没有这一项,那么就给image赋值placeholder。
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }

    //存在url
    if (url) {

        // check if activityView is enabled or not
        //是否添加菊花view
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }

        //下载图片的方法
        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            //停止转菊花
            [wself removeActivityIndicator];
            if (!wself) return;
            dispatch_main_sync_safe(^{
                //主线程同步执行
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {//有图片,并且需要对图片进行处理,那么就调用这个block
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    //设置图片
                    wself.image = image;
                    //刷新
                    [wself setNeedsLayout];
                } else {
                    //如果没有图片,需要加载图片信息
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];

        //将改下载的operation添加到operation的字典中
        [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);
            }
        });
    }
}

这个方法分为三步:(1)将原来的operation停止(具体原因后面解释)(2)设置placeholder(3)下载图片
在第三步中下载,下载图片的方法会返回一个id SDWebImageOperation类型的operation,将这个operation添加到以“UIImageViewImageLoad”为key的字典中 具体 这个operation是个什么东东(猜想大概和NSOperation多少有点关系)。下一篇我们再来看这个方法,我们先把这个.m文件搞定

在.h文件中还有一些其他方法

    还有一些其他方法:
    加载一组图片
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs;
    取消当前图片加载
- (void)sd_cancelCurrentImageLoad;
    取消当前一组动画的加载
- (void)sd_cancelCurrentAnimationImagesLoad;
    是否显示小菊花
- (void)setShowActivityIndicatorView:(BOOL)show;
    小菊花的样式
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style;

我们来看看这些方法是干什么的

- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
      //取消当前正在执行的operation
    [self sd_cancelCurrentAnimationImagesLoad];
    __weak __typeof(self)wself = self;

    NSMutableArray *operationsArray = [[NSMutableArray alloc] init];
        //循环去下载这些图片然后将返回的每个operation放在operationsArray数组中,最后放在大字典里
    for (NSURL *logoImageURL in arrayOfURLs) {
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            if (!wself) return;
            dispatch_main_sync_safe(^{
                __strong UIImageView *sself = wself;
                [sself stopAnimating];
                if (sself && image) {
                    NSMutableArray *currentImages = [[sself animationImages] mutableCopy];
                    if (!currentImages) {
                        currentImages = [[NSMutableArray alloc] init];
                    }
                    [currentImages addObject:image];

                    sself.animationImages = currentImages;
                    [sself setNeedsLayout];
                }
                [sself startAnimating];
            });
        }];
        [operationsArray addObject:operation];
    }

    [self sd_setImageLoadOperation:[NSArray arrayWithArray:operationsArray] forKey:@"UIImageViewAnimationImages"];
}

发现还是最后调用的下载图片的那个方法,只不过这里是多张图片,使用循环方式去下载的。

下面这一坨代码又是干什么用的呢,熟悉category的哦那同学一看就应该知道吧。平时我们是对一个类进行扩展的时候,一般是添加方法,不添加属性。这里就用到了runtime的两个方法:objc_getAssociatedObject和objc_setAssociatedObject,将一个属性跟这个类进行关联,这两个函数百度上解释额很清楚我就不在这里解释了,你看懂了这两个方法就知道下面的代码时干什么的了,平时写代码的时候也能用到,能有效提升逼格。

- (UIActivityIndicatorView *)activityIndicator {
    return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}

- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
    objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}

- (void)setShowActivityIndicatorView:(BOOL)show{
    objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN);
}

- (BOOL)showActivityIndicatorView{
    return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}

- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
    objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}

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

最后两个方法一个是添加菊花、一个是移除菊花。注意在添加菊花中是通过代码在activityIndicator上添加约束,这个大家就自行搜索吧。
最后说两个宏

如果是主线程之间就直接调用block,如果不是就在主线程中使用同步函数调用block
#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }
   如果是主线程之间就直接调用block,如果不是就在主线程中使用异步函数调用block
#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }

到此为止,这个类中的文件中方法、属性的解析都出来了。遗留两个问题,一个是核心方法中刚开始取消操作的原因,以及核心方法中的下载图片的方法的原理,我们在下一篇中重点介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值