当前位置: 代码网 > it编程>App开发>苹果IOS > 源码解析ios开发SDWebImage方法

源码解析ios开发SDWebImage方法

2024年05月19日 苹果IOS 我要评论
引言在着手写第二篇的时候,发现这个sdwebimage确实吧nsoperation用的太美了。确实可能帮你理解nsoperation和nsoperationqueue,当然还有block的队列。还有一

引言

在着手写第二篇的时候,发现这个sdwebimage确实吧nsoperation用的太美了。确实可能帮你理解nsoperationnsoperationqueue,当然还有block的队列。还有一个gcd

各位看官在看的时候可以着重的看看他的operatinqueue的队列。看看是怎么添加到队列的以及是怎么移除队列。在后面的文章就会提到他是怎么执行的。 还要注重看的就是以前用的nsurlconnection而现在用的nsurlsession下来了

最近在面试,发现有人问这个组件,所有读一读,应付一下面试吧!!

源码解析

废话不多说看源码。

  • 1:在组件中提供了很多类似这样的方法
- (void)sd_setimagewithurl:(nullable nsurl *)url;
- (void)sd_setimagewithurl:(nullable nsurl *)url
          placeholderimage:(nullable uiimage *)placeholder;
  • 2:于是乎所有的方法都会调用下面的这个方法
- (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
  • 3:下面具体阅读代码 第一行执行的代码
//********1: 所有的设置控件图片都是经过该方法*******
nsstring *validoperationkey = operationkey ?: nsstringfromclass([self class]);
//********2: 取消当前控件正在operations的队列*******
[self sd_cancelimageloadoperationwithkey:validoperationkey];

解析:

1.在第一行创建了validoperationkey的operationkey,是以当前扩展的类名命名。

2.执行sd_cancelimageloadoperationwithkey方法

- (void)sd_cancelimageloadoperationwithkey:(nullable nsstring *)key {
    // cancel in progress downloader from queue
    /**
     * 在该对象中,用runtime手动的添加一个字典属性。
     ### 说一下这里的operationdictionary
     ### 该字典的value是下载的操作,然而key是对视图和操作来做的标识字符串
    */
    sdoperationsdictionary *operationdictionary = [self operationdictionary];
    /*
     * 在生成字典中的都是新的,所有都没有数据
     */
    id operations = operationdictionary[key];
    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];
    }
}

字典操作

在看一下去字典操作[self operationdictionary]

- (sdoperationsdictionary *)operationdictionary {
    /**
     ### 这里有一个疑问?
     这样创建出来的字典每一次都是新的
    虽然用了 static char loadoperationkey,但是没一次创建的地址都是新的,并且该字典还是空的。
     创建完成之后都直接返回了。不知道每次创建的都是新的并且还是空的字典,这样的开销会很大的??!!!,也希望各位看官给予解答啊???
     */
    sdoperationsdictionary *operations = objc_getassociatedobject(self, &loadoperationkey);
    // 创建成功返回该字典,直接把该字典当做属性返回
    if (operations) {
        return operations;
    }
    operations = [nsmutabledictionary dictionary];
    objc_setassociatedobject(self, &loadoperationkey, operations, objc_association_retain_nonatomic);
    return operations;
}

看到这里我们在返回继续看 这个函数的代码有点长,耐心看完啊。。。

- (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 {
    //********1: 所有的设置控件图片都是经过该方法*******
    nsstring *validoperationkey = operationkey ?: nsstringfromclass([self class]);
    /**2: 取消当前控件正在operations的队列
     * 至于为什么在该控件下载前都要清空该控件对应的所有的下载队列?
     * 可能是,如果要给控件赋值新的图片的话,之前所有的操作都和当前的操作无关,所有就取消吧
    *******/
    [self sd_cancelimageloadoperationwithkey:validoperationkey];
    objc_setassociatedobject(self, &imageurlkey, url, objc_association_retain_nonatomic);
    /**3:设置占位图片*/
    /**这里的意思就是options参数不是sdwebimagedelayplaceholder,就执行以下操作   */
    if (!(options & sdwebimagedelayplaceholder)) {
        /**
         * 注意这的里宏定义
         * #ifndef dispatch_main_async_safe
         * #define dispatch_main_async_safe(block)\
         * if (strcmp(dispatch_queue_get_label(dispatch_current_queue_label), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
         * block();\
         * } else {\
         * dispatch_async(dispatch_get_main_queue(), block);\
         * }
         * #endif
         可以看出都把block方法主线程中去执行。
         应为在系统里,只能有一个线程去执行ui的更新,那就是主线程。
         如果能在其他线程更新,那这世界就乱了,像了解
         更仔细的问题可以关注www.osjoin.com查看是为什么!
         */
        dispatch_main_async_safe(^{
            /**设置占位图placeholder*/
            [self sd_setimage:placeholder imagedata:nil basedonclassorviacustomsetimageblock:setimageblock];
        });
    }
    /**4:菊花提示*/
    if (url) {
        // check if activityview is enabled or not
        if ([self sd_showactivityindicatorview]) {
            [self sd_addactivityindicator];
        }
        __weak __typeof(self)wself = self;
        /**5:下载图片
         * 进入下载图片最重要的函数也是核心的函数了
         这个函数关联的函数较多,先简单过一下。
         */
        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;
                }
                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);
                }
            });
        }];
        /**将行的下操作放到uiview的下载队列中的自定义的字典中去*/
        [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);
            }
        });
    }
}

看一下调用下载函数前的实例化过程

这个loadimagewithurl函数在sdwebimagemanager,并且是用单列调用,

+ (nonnull instancetype)sharedmanager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
- (nonnull instancetype)init {
    /**
     ###此处有其他重要配置,下一篇文章解读
     ###此处有其他重要配置,下一篇文章解读
     ###此处有其他重要配置,下一篇文章解读
     */
    sdimagecache *cache = [sdimagecache sharedimagecache];
    sdwebimagedownloader *downloader = [sdwebimagedownloader shareddownloader];
    return [self initwithcache:cache downloader:downloader];
}
- (nonnull instancetype)initwithcache:(nonnull sdimagecache *)cache downloader:(nonnull sdwebimagedownloader *)downloader {
    if ((self = [super init])) {
        _imagecache = cache;
        _imagedownloader = downloader;
        /**这里的failedurls是nsset也就没重复的属性*/
        _failedurls = [nsmutableset new];
        _runningoperations = [nsmutablearray new];
    }
    return self;
}

上面都是初始化的操作,然后看下面的函数

- (id <sdwebimageoperation>)loadimagewithurl:(nullable nsurl *)url
                                     options:(sdwebimageoptions)options
                                    progress:(nullable sdwebimagedownloaderprogressblock)progressblock
                                   completed:(nullable sdinternalcompletionblock)completedblock {
    // invoking this method without a completedblock is pointless
    nsassert(completedblock != nil, @"if you mean to prefetch the image, use -[sdwebimageprefetcher prefetchurls] instead");
    // very common mistake is to send the url using nsstring object instead of nsurl. for some strange reason, xcode won't
    // throw any warning for this type mismatch. here we failsafe this error by allowing urls to be passed as nsstring.
    /**
     这里防止用户输入的类型错误,转换一下
     */
    if ([url iskindofclass:nsstring.class]) {
        url = [nsurl urlwithstring:(nsstring *)url];
    }
    // prevents app crashing on argument type error like sending nsnull instead of nsurl
    /*! 如果转换失败,url为nil 返回 */
    if (![url iskindofclass:nsurl.class]) {
        url = nil;
    }
    /**5.1:搞一个新的下载队列*/
    /*! 这里就又__block和__weak的用法区别
     这里简单说说一下
     1:__block,在block函数里可以修改和阅读
     2:__weak可以避免循环引用,在给他设置新数据的时候,设置方法既不保留新值,也不释放旧值
     */
    /*! 说一下sdwebimagecombinedoperation
     他拥有了一个缓存队列和一个能取消执行的block,并且还遵守了sdwebimageoperation一个协议
     */
    __block sdwebimagecombinedoperation *operation = [sdwebimagecombinedoperation new];
    __weak sdwebimagecombinedoperation *weakoperation = operation;
    /**5.2判断url是否是下载失败的url*/
    bool isfailedurl = no;
    if (url) {
        /*! 创建一个互斥锁防止有其他线程同时修改该对象 */
        @synchronized (self.failedurls) {
            isfailedurl = [self.failedurls containsobject:url];
        }
    }
    /**
     * 5.3url不存在或者下载失败的url 则不在继续下载队列
     * 返回一个completedblock
     */
    if (url.absolutestring.length == 0 || (!(options & sdwebimageretryfailed) && isfailedurl)) {
        [self callcompletionblockforoperation:operation completion:completedblock error:[nserror errorwithdomain:nsurlerrordomain code:nsurlerrorfiledoesnotexist userinfo:nil] url:url];
        return operation;
    }
    /**5.4把url添加在下载队列
     把operation加入到self.runningoperations的数组里面,
     并创建一个互斥线程锁来保护这个操作
     */
    @synchronized (self.runningoperations) {
        [self.runningoperations addobject:operation];
    }
    /*! 获取image的url对应的key */
    nsstring *key = [self cachekeyforurl:url];
    /**5.5快速查找***缓存*****/
    operation.cacheoperation = [self.imagecache querycacheoperationforkey:key done:^(uiimage *cachedimage, nsdata *cacheddata, sdimagecachetype cachetype) {
        /**
         * 这里的状态改变,在sd_cancelimageloadoperationwithkey方法中调用代理函数时会改变该状态cancel
         * 如果该队列是取消状态,直接从下载队列中移除,此处有一个安全锁
         */
        if (operation.iscancelled) {
            [self safelyremoveoperationfromrunning:operation];
            return;
        }
        /**下载完成之后执行图片转换处理和缓存操作**/
        //条件1:在缓存中没有找到图片或者options选项里面包含了sdwebimagerefreshcached(这两项都需要进行请求网络图片的)
        //条件2:代理允许下载,sdwebimagemanagerdelegate的delegate不能响应imagemanager:shoulddownloadimageforurl:方法或者能响应方法且方法返回值为yes.也就是没有实现这个方法就是允许的,如果实现了的话,返回yes才是允许
        if ((!cachedimage || options & sdwebimagerefreshcached) &&
            (![self.delegate respondstoselector:@selector(imagemanager:shoulddownloadimageforurl:)] || [self.delegate imagemanager:self shoulddownloadimageforurl:url])) {
            //如果在缓存中找到了image且options选项包含sdwebimagerefreshcached,先在主线程完成一次回调,使用的是缓存中找的图片
            if (cachedimage && options & sdwebimagerefreshcached) {
                // if image was found in the cache but sdwebimagerefreshcached is provided, notify about the cached image
                // and try to re-download it in order to let a chance to nsurlcache to refresh it from server.
                /** 如果在缓存中找到了image但是设置了sdwebimagerefreshcached选项,传递缓存的image,同时尝试重新下载它来让nsurlcache有机会接收服务器端的更新
                dispatch_main_async_safe(^{
                    if (operation && !operation.iscancelled && completionblock) {
                        completionblock(image, data, error, cachetype, finished, url);
                    }
                });
                 */
                [self callcompletionblockforoperation:weakoperation completion:completedblock image:cachedimage data:cacheddata error:nil cachetype:cachetype finished:yes url:url];
            }
            // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
            // download if no image or requested to refresh anyway, and download allowed by delegate
            sdwebimagedownloaderoptions downloaderoptions = 0;
            if (options & sdwebimagelowpriority) downloaderoptions |= sdwebimagedownloaderlowpriority;
            if (options & sdwebimageprogressivedownload) downloaderoptions |= sdwebimagedownloaderprogressivedownload;
            if (options & sdwebimagerefreshcached) downloaderoptions |= sdwebimagedownloaderusensurlcache;
            if (options & sdwebimagecontinueinbackground) downloaderoptions |= sdwebimagedownloadercontinueinbackground;
            if (options & sdwebimagehandlecookies) downloaderoptions |= sdwebimagedownloaderhandlecookies;
            if (options & sdwebimageallowinvalidsslcertificates) downloaderoptions |= sdwebimagedownloaderallowinvalidsslcertificates;
            if (options & sdwebimagehighpriority) downloaderoptions |= sdwebimagedownloaderhighpriority;
            if (options & sdwebimagescaledownlargeimages) downloaderoptions |= sdwebimagedownloaderscaledownlargeimages;
            if (cachedimage && options & sdwebimagerefreshcached) {
                // force progressive off if image already cached but forced refreshing
                // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项
                downloaderoptions &= ~sdwebimagedownloaderprogressivedownload;
                // ignore image read from nsurlcache if image if cached but force refreshing
                // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从nsurlcache读取的image
                downloaderoptions |= sdwebimagedownloaderignorecachedresponse;
            }
            /**如果在缓存和硬盘上都没查到url对应的图片
             ***则进行图片下载
             */
            /*! 进入下载操作就是2.2中的操作了*/
            sdwebimagedownloadtoken *suboperationtoken = [self.imagedownloader downloadimagewithurl:url
                                                                                            options:downloaderoptions
                                                                                           progress:progressblock
                                                                                          completed:^(uiimage *downloadedimage,
                                                                                                      nsdata *downloadeddata,
                                                                                                      nserror *error,
                                                                                                      bool finished) {
                __strong __typeof(weakoperation) strongoperation = weakoperation;
                                                                                              /*! 如果为取消状态,啥也不错,闲着 */
                if (!strongoperation || strongoperation.iscancelled) {
                    // do nothing if the operation was cancelled
                    // 不用做任何事情,如果是取消状态
                    // see #699 for more details
                    // if we would call the completedblock, there could be a race condition between this block and another completedblock for the same object, so if this one is called second, we will overwrite the new data
                    //如果我们调用completedblock,这个block会和另外一个completedblock争夺一个对象,因此这个block被调用后会覆盖新的数据
                } else if (error) {
                    //进行完成回调
                    [self callcompletionblockforoperation:strongoperation completion:completedblock error:error url:url];
                    if (   error.code != nsurlerrornotconnectedtointernet
                        && error.code != nsurlerrorcancelled
                        && error.code != nsurlerrortimedout
                        && error.code != nsurlerrorinternationalroamingoff
                        && error.code != nsurlerrordatanotallowed
                        && error.code != nsurlerrorcannotfindhost
                        && error.code != nsurlerrorcannotconnecttohost) {
                        //将失败的url添加到failedurls的set中去
                        @synchronized (self.failedurls) {
                            [self.failedurls addobject:url];
                        }
                    }
                }
                else {
                    //如果有重试状态,将url从失败列表中移除
                    if ((options & sdwebimageretryfailed)) {
                        @synchronized (self.failedurls) {
                            [self.failedurls removeobject:url];
                        }
                    }
                    bool cacheondisk = !(options & sdwebimagecachememoryonly);
                    //options包含了sdwebimagerefreshcached选项,且缓存中找到了image且没有下载成功
                    if (options & sdwebimagerefreshcached && cachedimage && !downloadedimage) {
                        // image refresh hit the nsurlcache cache, do not call the completion block
                    } else if (
                               //图片下载成功并且 设置了需要变形image的选项且变形的代理方法已经实现
                               downloadedimage &&
                               (!downloadedimage.images || (options & sdwebimagetransformanimatedimage)) &&[self.delegate respondstoselector:@selector(imagemanager:transformdownloadedimage:withurl:)]
                               ) {
                        /**
                         * dispatch_get_global_queue创建的一个//全局队列异步队列执行
                         */
                        dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_high, 0), ^{
                            //调用代理方法完成图片transform
                            uiimage *transformedimage = [self.delegate imagemanager:self transformdownloadedimage:downloadedimage withurl:url];
                            if (transformedimage && finished) {
                                bool imagewastransformed = ![transformedimage isequal:downloadedimage];
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                //对已经transform的图片进行缓存
                                [self.imagecache storeimage:transformedimage imagedata:(imagewastransformed ? nil : downloadeddata) forkey:key todisk:cacheondisk completion:nil];
                            }
                            /*! 回到主线的调度 */
                            [self callcompletionblockforoperation:strongoperation completion:completedblock image:transformedimage data:downloadeddata error:nil cachetype:sdimagecachetypenone finished:finished url:url];
                        });
                    } else {
                        //如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
                        if (downloadedimage && finished) {
                            [self.imagecache storeimage:downloadedimage imagedata:downloadeddata forkey:key todisk:cacheondisk completion:nil];
                        }
                        /*! 回到主线的调度 */
                        [self callcompletionblockforoperation:strongoperation completion:completedblock image:downloadedimage data:downloadeddata error:nil cachetype:sdimagecachetypenone finished:finished url:url];
                    }
                }
                /**
                 * 从正在进行的操作列表中移除这组合操作
                 * 此处有一个安全锁保证线程安全
                 */
                if (finished) {
                    [self safelyremoveoperationfromrunning:strongoperation];
                }
            }];
            /**取消的回调*/
            operation.cancelblock = ^{
                [self.imagedownloader cancel:suboperationtoken];
                __strong __typeof(weakoperation) strongoperation = weakoperation;
                [self safelyremoveoperationfromrunning:strongoperation];
            };
            //在缓存中找到图片(代理不允许下载 或者没有设置sdwebimagerefreshcached选项  满足至少一项)
        } else if (cachedimage) {
            __strong __typeof(weakoperation) strongoperation = weakoperation;
            [self callcompletionblockforoperation:strongoperation completion:completedblock image:cachedimage data:cacheddata error:nil cachetype:cachetype finished:yes url:url];
            [self safelyremoveoperationfromrunning:operation];
        } else {
            //缓存中没有扎到图片且代理不允许下载
            // image not in cache and download disallowed by delegate
            __strong __typeof(weakoperation) strongoperation = weakoperation;
            [self callcompletionblockforoperation:strongoperation completion:completedblock image:nil data:nil error:nil cachetype:sdimagecachetypenone finished:yes url:url];
            [self safelyremoveoperationfromrunning:operation];
        }
    }];
    return operation;
}

这个函数就进入了sdwebimage缓存的策略了。

先说一下他的这一个策略缓存。

*1:大家都是sdwebiamge都是先从缓存上查找,如果有就直接显示

*2:如果不存在就在沙盒中查找 

  • *2.1如果存在,则把沙盒中的图片添加到imagecache中,取出显示 
  • *2.2 如果不存在在显示占位图,根据url在operationcache是否存在下载操作 

*2.2.1 如果存在,说明该图片正在下载。

*2.2.2如果不存在,创建图片下载操作,放到operationcache中

  • * 2.3 下载完成,将当前操作队列从operationcache中移除。并将下载的图片的添加在imagecache中。显示

先慢慢体会一下。。。(停留30秒)

开始进入查找函数

- (nullable nsoperation *)querycacheoperationforkey:(nullable nsstring *)key done:(nullable sdcachequerycompletedblock)doneblock {
    /**从缓存中查找图片开始*/
    /*! 检查key是否为空(url) */
    if (!key) {
        if (doneblock) {
            doneblock(nil, nil, sdimagecachetypenone);
        }
        return nil;
    }
    // 先检查缓存--内存上的数据
    // first check the in-memory cache...
    uiimage *image = [self imagefrommemorycacheforkey:key];
    if (image) {
        /**从内存在检查到有图片**/
        nsdata *diskdata = nil;
        if ([image isgif]) {
            diskdata = [self diskimagedatabysearchingallpathsforkey:key];
        }
        /**如果有直接返回在view上显示*/
        if (doneblock) {
            doneblock(image, diskdata, sdimagecachetypememory);
        }
        return nil;
    }
    /**如果内存上没有数据,则在硬盘上查找,如果找到了该图片,就放到缓存上用doneblock完成回调**/
    /*! 这一块创建了异步队列
     这里的self.ioqueue是这样定义的
     ****@property (strong, nonatomic, nullable) dispatch_queue_t ioqueue;
     ****这里开始了串行的队列去处理硬盘上的缓存
     */
    nsoperation *operation = [nsoperation new];
    dispatch_async(self.ioqueue, ^{
        /**如果是取消的 就直接返回*/
        if (operation.iscancelled) {
            // do not call the completion if cancelled
            return;
        }
        /*! 开了手动释放池 */
        @autoreleasepool {
            /**从磁盘中读取图片*/
            /*! 根据url去硬盘上查找数据 */
            nsdata *diskdata = [self diskimagedatabysearchingallpathsforkey:key];
            uiimage *diskimage = [self diskimageforkey:key];
            if (diskimage && self.config.shouldcacheimagesinmemory) {
                nsuinteger cost = sdcachecostforimage(diskimage);
                /**如果在硬盘中读取到图片,则把沙盒中的图片放到cache中*/
                //self.memcache是nscache创建的一个对象
                [self.memcache setobject:diskimage forkey:key cost:cost];
            }
            if (doneblock) {
                /*! 在主线程放回数据 */
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneblock(diskimage, diskdata, sdimagecachetypedisk);
                });
            }
        }
    });
    return operation;
}

快速查找缓存的方法回调

看完该函数以后在回到上面的看这个快速查找缓存的方法回调

operation.cacheoperation = [self.imagecache querydiskcacheforkey:key done:^(uiimage *image, sdimagecachetype cachetype) {
        if (operation.iscancelled) {
            @synchronized (self.runningoperations) {
                [self.runningoperations removeobject:operation];
            }
            return;
        }
        if ((!image || options & sdwebimagerefreshcached) && (![self.delegate respondstoselector:@selector(imagemanager:shoulddownloadimageforurl:)] || [self.delegate imagemanager:self shoulddownloadimageforurl:url])) {
//如果在缓存中找到了image且options选项包含sdwebimagerefreshcached,先在主线程完成一次回调,使用的是缓存中找的图片
            if (image && options & sdwebimagerefreshcached) {
                dispatch_main_sync_safe(^{
                // 如果在缓存中找到了image但是设置了sdwebimagerefreshcached选项,传递缓存的image,同时尝试重新下载它来让nsurlcache有机会接收服务器端的更新
                    completedblock(image, nil, cachetype, yes, url);
                });
            }
            // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
            sdwebimagedownloaderoptions downloaderoptions = 0;
            //开始各种options的判断
            if (options & sdwebimagelowpriority) downloaderoptions |= sdwebimagedownloaderlowpriority;
            if (options & sdwebimageprogressivedownload) downloaderoptions |= sdwebimagedownloaderprogressivedownload;
            if (options & sdwebimagerefreshcached) downloaderoptions |= sdwebimagedownloaderusensurlcache;
            if (options & sdwebimagecontinueinbackground) downloaderoptions |= sdwebimagedownloadercontinueinbackground;
            if (options & sdwebimagehandlecookies) downloaderoptions |= sdwebimagedownloaderhandlecookies;
            if (options & sdwebimageallowinvalidsslcertificates) downloaderoptions |= sdwebimagedownloaderallowinvalidsslcertificates;
            if (options & sdwebimagehighpriority) downloaderoptions |= sdwebimagedownloaderhighpriority;
            if (image && options & sdwebimagerefreshcached) {
            // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项
                downloaderoptions &= ~sdwebimagedownloaderprogressivedownload;
               // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从nsurlcache读取的image
                downloaderoptions |= sdwebimagedownloaderignorecachedresponse;
            }
            //创建下载操作,先使用self.imagedownloader下载
            id <sdwebimageoperation> suboperation = [self.imagedownloader downloadimagewithurl:url options:downloaderoptions progress:progressblock completed:^(uiimage *downloadedimage, nsdata *data, nserror *error, bool finished) {
                __strong __typeof(weakoperation) strongoperation = weakoperation;
                if (!strongoperation || strongoperation.iscancelled) {
                    // do nothing if the operation was cancelled
                    //如果操作取消了,不做任何事情
                    // if we would call the completedblock, there could be a race condition between this block and another completedblock for the same object, so if this one is called second, we will overwrite the new data
                //如果我们调用completedblock,这个block会和另外一个completedblock争夺一个对象,因此这个block被调用后会覆盖新的数据
                }
                else if (error) {
                    //进行完成回调
                    dispatch_main_sync_safe(^{
                        if (strongoperation && !strongoperation.iscancelled) {
                            completedblock(nil, error, sdimagecachetypenone, finished, url);
                        }
                    });
                  //将url添加到失败列表里面
                    if (   error.code != nsurlerrornotconnectedtointernet
                        && error.code != nsurlerrorcancelled
                        && error.code != nsurlerrortimedout
                        && error.code != nsurlerrorinternationalroamingoff
                        && error.code != nsurlerrordatanotallowed
                        && error.code != nsurlerrorcannotfindhost
                        && error.code != nsurlerrorcannotconnecttohost) {
                        @synchronized (self.failedurls) {
                            [self.failedurls addobject:url];
                        }
                    }
                }
                else {
                    //如果设置了下载失败重试,将url从失败列表中去掉
                    if ((options & sdwebimageretryfailed)) {
                        @synchronized (self.failedurls) {
                            [self.failedurls removeobject:url];
                        }
                    }
                    bool cacheondisk = !(options & sdwebimagecachememoryonly);
        //options包含了sdwebimagerefreshcached选项,且缓存中找到了image且没有下载成功
                    if (options & sdwebimagerefreshcached && image && !downloadedimage) {
                        // image refresh hit the nsurlcache cache, do not call the completion block
 // 图片刷新遇到了nssurlcache中有缓存的状况,不调用完成回调。
                }
  //图片下载成功并且 设置了需要变形image的选项且变形的代理方法已经实现
                    else if (downloadedimage && (!downloadedimage.images || (options & sdwebimagetransformanimatedimage)) && [self.delegate respondstoselector:@selector(imagemanager:transformdownloadedimage:withurl:)]) {
//全局队列异步执行                      dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_high, 0), ^{
                            //调用代理方法完成图片transform
                            uiimage *transformedimage = [self.delegate imagemanager:self transformdownloadedimage:downloadedimage withurl:url];
                            if (transformedimage && finished) {
                                bool imagewastransformed = ![transformedimage isequal:downloadedimage];
                //对已经transform的图片进行缓存
                                [self.imagecache storeimage:transformedimage recalculatefromimage:imagewastransformed imagedata:(imagewastransformed ? nil : data) forkey:key todisk:cacheondisk];
                            }
                            //主线程执行完成回调
                            dispatch_main_sync_safe(^{
                                if (strongoperation && !strongoperation.iscancelled) {
                                    completedblock(transformedimage, nil, sdimagecachetypenone, finished, url);
                                }
                            });
                        });
                    }
//如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
                    else {
                        if (downloadedimage && finished) {
                            [self.imagecache storeimage:downloadedimage recalculatefromimage:no imagedata:data forkey:key todisk:cacheondisk];
                        }
                   //主线程完成回调 
                        dispatch_main_sync_safe(^{
                            if (strongoperation && !strongoperation.iscancelled) {
                                completedblock(downloadedimage, nil, sdimagecachetypenone, finished, url);
                            }
                        });
                    }
                }
                if (finished) {
       // 从正在进行的操作列表中移除这组合操作
                    @synchronized (self.runningoperations) {
                        if (strongoperation) {
                            [self.runningoperations removeobject:strongoperation];
                        }
                    }
                }
            }];
          //设置组合操作取消得得回调
            operation.cancelblock = ^{
                [suboperation cancel];
                @synchronized (self.runningoperations) {
                    __strong __typeof(weakoperation) strongoperation = weakoperation;
                    if (strongoperation) {
                        [self.runningoperations removeobject:strongoperation];
                    }
                }
            };
        }
//处理其他情况
//case1.在缓存中找到图片(代理不允许下载 或者没有设置sdwebimagerefreshcached选项  满足至少一项)
        else if (image) {
            //完成回调
            dispatch_main_sync_safe(^{
                __strong __typeof(weakoperation) strongoperation = weakoperation;
                if (strongoperation && !strongoperation.iscancelled) {
                    completedblock(image, nil, cachetype, yes, url);
                }
            });
          //从正在进行的操作列表中移除组合操作
            @synchronized (self.runningoperations) {
                [self.runningoperations removeobject:operation];
            }
        }
          //case2:缓存中没有扎到图片且代理不允许下载
        else {
        //主线程执行完成回调
            dispatch_main_sync_safe(^{
                __strong __typeof(weakoperation) strongoperation = weakoperation;
                if (strongoperation && !weakoperation.iscancelled) {
                    completedblock(nil, nil, sdimagecachetypenone, yes, url);
                }
            });
          //从正在执行的操作列表中移除组合操作
            @synchronized (self.runningoperations) {
                [self.runningoperations removeobject:operation];
            }
        }
    }];

总结一下函数调用

1.先调用

- (void)sd_setimagewithurl:(nullable nsurl *)url
          placeholderimage:(nullable uiimage *)placeholder;

2.设置图片

- (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
  • 2.1 取消该控件对应的之前的所有的下载操作
- (void)sd_cancelimageloadoperationwithkey:(nullable nsstring *)key;
  • 2.2 开始根据图片的url做为key去查找
- (id <sdwebimageoperation>)loadimagewithurl:(nullable nsurl *)url
                                     options:(sdwebimageoptions)options
                                    progress:(nullable sdwebimagedownloaderprogressblock)progressblock
                                   completed:(nullable sdinternalcompletionblock)completedblock

2.2.1 查找内存和硬盘上的缓存

- (nullable nsoperation *)querycacheoperationforkey:(nullable nsstring *)key done:(nullable sdcachequerycompletedblock)doneblock;
  • 2.3 创建下载队列下载图片
- (nullable sdwebimagedownloadtoken *)downloadimagewithurl:(nullable nsurl *)url
                                                   options:(sdwebimagedownloaderoptions)options
                                                  progress:(nullable sdwebimagedownloaderprogressblock)progressblock
                                                 completed:(nullable sdwebimagedownloadercompletedblock)completedblock;
  • 2.4 最后将进行的操作,放到view对应的opationsdicaionary的字典中。记录当前的操作队列
- (void)sd_setimageloadoperation:(nullable id)operation forkey:(nullable nsstring *)key

以上就是源码解析ios开发sdwebimage方法的详细内容,更多关于ios sdwebimage方法的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com