美文网首页iOS开发程序员移动端开发
SDWebImage源码解析(4)——知识点

SDWebImage源码解析(4)——知识点

作者: 无忘无往 | 来源:发表于2017-03-30 21:10 被阅读41次

利用runtime,在分类中添加属性

在SDWebImage为UIImage类做的扩展中,会记录当前加载的url。
那么image url是怎么在Category中存储和获取的呢?很简单,其实是运用了runtime的两个函数, 将id对象与image对象关联起来:

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

这个是一对儿函数,可以将id对象与object关联,以及通过key做索引,取得这个id对象。
这样,其实我们可以利用property的set/get方法,为category添加属性,例子:

#import <UIKit/UIKit.h>
@interface UIViewController (test)
@property(nonatomic, strong) NSString *myName;
@end
#import "UIViewController+test.h"
#import <objc/runtime.h>
static char myNameKey;

@implementation UIViewController (test)
- (void)setMyName:(NSString *)myName
{
    objc_setAssociatedObject(self, &myNameKey, myName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)myName
{
    return objc_getAssociatedObject(self, &myNameKey);
}
@end

这里注意两点:

  1. 这里的关联key是一个void *,因此我们可以将key当做一个指向静态存储区的指针:
static char myNameKey;
key : &myNameKey
  1. 要断开关联,我们只需要再调用关联函数,使用同样的key但是将关联id置为nil即可:
objc_setAssociatedObject(self, &myNameKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

此外还有一个断开所有关联的函数:

/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

利用返回的对象 对异步操作进行管理,用户可以调用该对象的cancel来取消掉当前的异步操作

我们看一下,SDWebImage返回了几种对象:

  1. 加载image,这里会有两种可能的操作(读取Cache, download image),所以,SDWebImage返回了
  SDWebImageCombinedOperation 对象
  @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
      @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
      @property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock; // 用于执行cancle 回调
      @property (strong, nonatomic) NSOperation *cacheOperation;  // 用于cancle 读cache的操作
  @end

这个对象遵循SDWebImageOperation协议。
该协议只有一个方法,就是cancel。
当用户调用cancle方法时,SDWebImageCombinedOperation对象会读 Cache 操作,download操作都取消掉。设计的思路很巧妙,有兴趣可以看一下。
我这里说下大体思路:

  - (void)cancel {
    self.cancelled = YES;   // 设置cancel的标记位,若异步操作返回,先判断当前的combined对象的cancle标记位,若YES,则直接返回,而不调用finish block
    if (self.cacheOperation) {   // 这里调用代表读cache操作的operation的cancel方法
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();  // 在cancle block中会调用download 返回的nsoperation 对象的cancel;
        
        // TODO: this is a temporary fix to #809.
        // Until we can figure the exact cause of the crash, going with the ivar instead of the setter
      self.cancelBlock = nil;
        _cancelBlock = nil;
    }
}
  1. 当读取Cache时,会返回一个NSOperation对象
  /**
   * Query the disk cache asynchronously.
   *
   * @param key The unique key used to store the wanted image
   */
  - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:    (SDWebImageQueryCompletedBlock)doneBlock;
  1. 当download image时,返回一个符合SDWebImageOperation协议的对象(SDWebImage其实是返回了一个NSOperation对象)
  /**
   * Creates a SDWebImageDownloader async downloader instance with a given URL
   *
   * The delegate will be informed when the image is finish downloaded or an error has happen.
   *
   * @see SDWebImageDownloaderDelegate
   *
   * @param url            The URL to the image to download
   * @param options        The options to be used for this download
   * @param progressBlock  A block called repeatedly while the image is downloading
   * @param completedBlock A block called once the download is completed.
   *                       If the download succeeded, the image parameter is set, in case of error,
   *                       error parameter is set with the error. The last parameter is always YES
   *                       if SDWebImageDownloaderProgressiveDownload isn't use. With the
   *                       SDWebImageDownloaderProgressiveDownload option, this block is called
   *                       repeatedly with the partial image object and the finished argument set to NO
   *                       before to be called a last time with the full image and finished argument
   *                       set to YES. In case of error, the finished argument is always YES.
   *
   * @return A cancellable SDWebImageOperation
   */
  - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageDownloaderOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageDownloaderCompletedBlock)completedBlock;

利用GCD的串行队列以及dispatch_async异步派发,在保证线程安全的同时,提高效率

我们有些操作,需要保证是线程安全的。
对于线程安全,有时候我们可能直观的想到加锁。
但是加锁有两个不好的地方:

  1. 容易造成死锁。
  2. 加锁会阻塞当前线程,使运行效率下降。

其实利用GCD,我们也可以实现线程安全,并避免使用锁的弊端。
在SDWebImage中,当读取disk上的缓存时,为了防止IO冲突,需要一个线程安全的环境来进行image的读写。SDWebImage是这么做的:

  1. 创建一个串行队列:
    @interface SDImageCache ()
    ...
    @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
    ...

     // Create IO serial queue
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
  1. 在进行IO操作时,利用dispatch_async,将任务提交到ioQueue中,同时由于是异步派发,因此不会阻塞当前线程,提高了效率。
  dispatch_async(self.ioQueue, ^{
    // do iO task
  });

利用GCD实现读写锁

利用GCD的读写锁,关键使用到了

  1. 栅栏:
dispatch_barrier_sync/async
  1. 一个并发队列

在SDWebImage中,对于每一个下载的回调block,SDWebImageDownloader都会存到:

@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;

其中,URLCallbacks的key是每个下载的url,而对应的value则是一个NSArray类型,该array存储了所有同一个url下载请求所对应的call back block。
由于这个URLCallbacks可能会被多线程读写,因此需要线程安全。我们可以做一个读写锁,使得读并发,写独占。
SDWebImage是这么实现的:

  1. 生成一个并发队列,用于访问URLCallbacks
  // This queue is used to serialize the handling of the network responses of all the download operation in a     single queue
  @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
  _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
  1. 在对URLCallbacks读的地方,调用dispatch_sync,并发读取
       dispatch_sync(sself.barrierQueue, ^{ 
                                                                 callbacksForURL = [sself.URLCallbacks[url] copy];
                                               });
  1. 在对URLCallbacks写的地方,调用dispatch_barrier_sync, 实现独占访问。
      dispatch_barrier_sync(sself.barrierQueue, ^{
                                                                callbacksForURL = [sself.URLCallbacks[url] copy];
                                                                if (finished) {
                                                                    [sself.URLCallbacks removeObjectForKey:url];
                                                                }
                                                            });

相关文章

  • SDWebImage

    1.SDWebImage源码解析(1)——总体架构,Cache读取2.SDWebImage源码解析(2)——ima...

  • SDWebImage源码解析(三)

    在前面的SDWebImage源码解析(一)和SDWebImage源码解析(二)中,解析了开源异步图片下载库SDWe...

  • SDWebImage源码解析(4)——知识点

    利用runtime,在分类中添加属性 在SDWebImage为UIImage类做的扩展中,会记录当前加载的url。...

  • SDWebImage源码解析(二)

    在SDWebImage源码解析(一)中,我从宏观上介绍了SDWebImage项目,并详细介绍了UIImageVie...

  • SDWebImage源码解析<二>

    前言 我们在第一篇文章《SDWebImage源码解析<一>》已经了解到SDWebImage是通过 SDWebIma...

  • SDWebImage源码解析

    SDWebImage是一个开源的第三方库,支持从远程服务器下载并缓存图片的功能。它具有以下功能: 提供UIImag...

  • SDWebImage源码解析

    概览 说到 iOS界的图片加载库,SDWebImage可谓无人不知,其简介的接口以及异步下载与缓存的强大功能,深受...

  • SDWebImage源码解析

    经历了春招失败的我,认识到自己的许多不足。现在终于决定静下心来,慢慢的补充自己知识的薄弱点,备战秋招。这也是我的第...

  • SDWebImage源码解析

    SDWebImage提供了简洁的对外接口,用户只需要调用- (void)sd_setImageWithURL:(n...

  • SDWebImage源码解析

    概述 SDWebImage是一个强大的图片下载框架,利用异步加载和内存+磁盘两级缓存处理,高效优雅的解决了图片下载...

网友评论

    本文标题:SDWebImage源码解析(4)——知识点

    本文链接:https://www.haomeiwen.com/subject/kjvpottx.html