一行行看SDWebImage源码

来源:互联网 发布:淘宝卖家怎么退货退款 编辑:程序博客网 时间:2024/04/28 19:21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * The image wasn't available the SDWebImage caches, but was downloaded from the web.
     该图像是不可用的SDWebImage缓存,但是从网络下载的.
     */
    SDImageCacheTypeNone,
    /**
     * The image was obtained from the disk cache.
     图像从磁盘高速缓存获得.
     */
    SDImageCacheTypeDisk,
    /**
     * The image was obtained from the memory cache.
     图像从存储器高速缓存获得
     */
    SDImageCacheTypeMemory
};

SDWebImage是iOS开发者经常使用的一个开源框架,这个框架的主要作用是:一个异步下载图片并且支持缓存的UIImageView分类。

UIImageView+WebCache

我们最常用的方法就是这个:

1
[_fineImageView sd_setImageWithURL:picURL placeholderImage:nil];

现在开始我们一步步地看这个方法的内部实现:

1
2
3
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

这里会调用下面这个方法:

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

我们看UIImageView+WebCache.h文件,我们可以发现为开发者提供了很多类似于- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder的方法。

这些方法最终都会调用- sd_setImageWithURL: placeholderImage: options: progress: completed:,这个方法可以算是核心方法,下面我们看一下- sd_setImageWithURL: placeholderImage: options: progress: completed:

方法的实现:

首先执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//移除UIImageView当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行这一行代码,保证这个ImageView的下载和缓存组合操作都被取消
[self sd_cancelCurrentImageLoad];
接下来我们来看看[self sd_cancelCurrentImageLoad]内部是怎么执行的:
- (void)sd_cancelCurrentImageLoad {
    [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}
//然后会调用UIView+WebCacheOperation的
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key
UIView+WebCacheOperation
下面我们先来看看UIView+WebCacheOperation里面都写了些什么:
UIView+WebCacheOperation这个分类提供了三个方法,用于操作绑定关系
#import #import "SDWebImageManager.h"
@interface UIView (WebCacheOperation)
/**
 *  Set the image load operation (storage in a UIView based dictionary)
设置图像加载操作(存储在和UIView做绑定的字典里面)
 *
 *  @param operation the operation
 *  @param key       key for storing the operation
 */
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;
/**
 *  Cancel all operations for the current UIView and key
  用这个key找到当前UIView上面的所有操作并取消
 *
 *  @param key key for identifying the operations
 */
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;
/**
 *  Just remove the operations corresponding to the current UIView and key without cancelling them
 *
 *  @param key key for identifying the operations
 */
- (void)sd_removeImageLoadOperationWithKey:(NSString *)key;

为了方便管理和找到视图正在进行的一些操作,WebCacheOperation将每一个视图的实例和它正在进行的操作(下载和缓存的组合操作)绑定起来,实现操作和视图一一对应关系,以便可以随时拿到视图正在进行的操作,控制其取消等,如何进行绑定我们在下面分析:

UIView+WebCacheOperation.m文件内

- (NSMutableDictionary *)operationDictionary用到了中定义的两个函数:

  • objc_setAssociatedObject

  • objc_getAssociatedObject

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
-(void)setAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}

objc_setAssociatedObject作用是对已存在的类在扩展中添加自定义的属性,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的。

objc_getAssociatedObject根据key获得与对象绑定的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (NSMutableDictionary *)operationDictionary {
/*
这个loadOperationKey 的定义是:static char loadOperationKey;
它对应的绑定在UIView的属性是operationDictionary(NSMutableDictionary类型)
operationDictionary的value是操作,key是针对不同类型视图和不同类型的操作设定的字符串
注意:&是一元运算符结果是右操作对象的地址(&loadOperationKey返回static char loadOperationKey的地址)
*/
    NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//如果可以查到operations,就rerun,反正给视图绑定一个新的,空的operations字典
    if (operations) {
        return operations;
    }
    operations = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    // 取消正在下载的队列
    NSMutableDictionary *operationDictionary = [self operationDictionary];
//如果 operationDictionary可以取到,根据key可以得到与视图相关的操作,取消他们,并根据key值,从operationDictionary里面删除这些操作
    id operations = [operationDictionary objectForKey:key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id  operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

接下来我们继续探索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- sd_setImageWithURL: placeholderImage: options: progress: completed:
- (void)sd_setImageWithURL:(NSURL *)url
          placeholderImage:(UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_cancelCurrentImageLoad];
    //将 url作为属性绑定到ImageView上,用static char imageURLKey作key
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/*options & SDWebImageDelayPlaceholder这是一个位运算的与操作,!(options & SDWebImageDelayPlaceholder)的意思就是options参数不是SDWebImageDelayPlaceholder,就执行以下操作
#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
*/

这是一个宏定义,因为图像的绘制只能在主线程完成,所以dispatch_main_sync_safe就是为了保证block在主线程中执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
//设置imageView的placeHolder
            self.image = placeholder;
        });
    }
    if (url) {
        // 检查是否通过`setShowActivityIndicatorView:`方法设置了显示正在加载指示器。如果设置了,使用`addActivityIndicator`方法向self添加指示器
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }
        __weak __typeof(self)wself = self;
//下载的核心方法
        id  operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options
progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
          //移除加载指示器
            [wself removeActivityIndicator];
          //如果imageView不存在了就return停止操作
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
/*
SDWebImageAvoidAutoSetImage,默认情况下图片会在下载完毕后自动添加给imageView,但是有些时候我们想在设置图片之前加一些图片的处理,就要下载成功后去手动设置图片了,不会执行`wself.image = image;`,而是直接执行完成回调,有用户自己决定如何处理。
*/
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
/*
如果后两个条件中至少有一个不满足,那么就直接将image赋给当前的imageView
,并调用setNeedsLayout
*/
                else if (image) {
                    wself.image = image;
                    [wself setNeedsLayout];
                else {
/*
image为空,并且设置了延迟设置占位图,会将占位图设置为最终的image,,并将其标记为需要重新布局。
  */
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
 // 为UIImageView绑定新的操作,以为之前把ImageView的操作cancel了
        [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);
            }
        });
    }
}

SDWebImageManager

-sd_setImageWithURL:forState:placeholderImage:options:completed:中,下载图片方法是位于SDWebImageManager类中- downloadImageWithURL: options:progress:completed:函数

我们看下文档是对SDWebImageManager怎么描述的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
 * It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
 * You can use this class directly to benefit from web image downloading with caching in another context than
 * a UIView.
 *
 * Here is a simple example of how to use SDWebImageManager:
 *
 * @code
/*
概述了SDWenImageManager的作用,其实UIImageVIew+WebCache这个Category背后执行操作的就是这个SDWebImageManager.它会绑定一个下载器也就是SDwebImageDownloader和一个缓存SDImageCache
*/
/**
 * Downloads the image at the given URL if not present in cache or return the cached version otherwise.
   若图片不在cache中,就根据给定的URL下载图片,否则返回cache中的图片
 *
 * @param url            The URL to the image
 * @param options        A mask to specify options to use for this request
 * @param progressBlock  A block called while image is downloading
 * @param completedBlock A block called when operation has been completed.
 *
 *   This parameter is required.
 *
 *   This block has no return value and takes the requested UIImage as first parameter.
 *   In case of error the image parameter is nil and the second parameter may contain an NSError.
 *
 *   The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
 *   or from the memory cache or from the network.
 *
 *   The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is
 *   downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
 *   block is called a last time with the full image and the last parameter set to YES.
 *
 * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
 */
/*
*第一个参数是必须的,就是image的url
*第二个参数options你可以定制化各种各样的操作
*第三个参数是一个回调block,用于图片下载过程中的回调
*第四个参数是一个下载完成的回调,会在图片下载完成后回调
*返回值是一个NSObject类,并且这个NSObject类是遵循一个协议这个协议叫SDWebImageOperation,这个协议里面只写了一个协议,就是一个cancel一个operation的协议
*/
- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

我们继续看SDWebImageManager .m

  • 初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
*初始化方法
*1.获得一个SDImageCache的单例
*2.获得一个SDWebImageDownloader的单例
*3.新建一个MutableSet来存储下载失败的url
*4.新建一个用来存储下载operation的可遍数组
*/
- (id)init {
    if ((self = [super init])) {
        _imageCache = [self createCache];
        _imageDownloader = [SDWebImageDownloader sharedDownloader];
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}

利用image的url生成一个缓存时需要的key,cacheKeyFilter的定义如下:

1
2
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

是一个可以返回一个字符串的block

1
2
3
4
5
6
7
8
9
10
//如果检测到cacheKeyFilter不为空的时候,利用cacheKeyFilter来生成一个key
//如果为空,那么直接返回URL的string内容,当做key.
- (NSString *)cacheKeyForURL:(NSURL *)url {
    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    }
    else {
        return [url absoluteString];
    }
}
  • 检查一个图片是否被缓存的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL)cachedImageExistsForURL:(NSURL *)url {
  //调用上面的方法取到image的url对应的key
    NSString *key = [self cacheKeyForURL:url];
//首先检测内存缓存中时候存在这张图片,如果已有直接返回yes
    if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES;
//如果内存缓存里面没有这张图片,那么就调用diskImageExistsWithKey这个方法去硬盘找
    return [self.imageCache diskImageExistsWithKey:key];
}
// 检测硬盘里是否缓存了图片
- (BOOL)diskImageExistsForURL:(NSURL *)url {
 NSString *key = [self cacheKeyForURL:url];
 return [self.imageCache diskImageExistsWithKey:key];
}

下面两个方法比较类似,都是先根据图片的url创建对应的key

  • 第一个方法先用BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);判断图片有没有在内存缓存中,如果图片在内存缓存中存在,就在主线程里面回调block,如果图片没有在内存缓存中就去查找是不是在磁盘缓存里面,然后在主线程里面回到block

  • 第二个方法只查询图片是否在磁盘缓存里面,然后在主线程里面回调block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];
    BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
    if (isInMemoryCache) {
        // making sure we call the completion block on the main queue
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completionBlock) {
                completionBlock(YES);
            }
        });
        return;
    }
    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];
    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}

上面的方法都是用于查询图片是否在内存缓存或磁盘的缓存下面的方法可以算是SDWebImageManager核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
//通过url建立一个operation用来下载图片.
- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
 //防止开发者把传入NSString类型的url,如果url的类型是NSString就给转换成NSURL类型
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    //如果转换NSURL失败,就把传入的url置为nil下载停止
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

首先我们先来看看__block和__weak的区别

__block用于指明当前声明的变量在被block捕获之后,可以在block中改变变量的值。因为在block声明的同时会截获该block所使用的全部自动变量的值,这些值只在block中只有"使用权"而不具有"修改权"。而block说明符就为block提供了变量的修改权,**block不能避免循环引用**,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj,blockObj = nil

__weak是所有权修饰符,__weak本身是可以避免循环引用的问题的,但是其会导致外部对象释放之后,block内部也访问不到对象的问题,我们可以通过在block内部声明一个__strong的变量来指向weakObj,使外部既能在block内部保持住又能避免循环引用

1
2
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;

我们再来看看SDWebImageCombinedOperation到底有一些什么内容:

SDWebImageCombinedOperation它什么也不做,保存了两个东西(一个block,可以取消下载operation,一个operation,cacheOperation用来下载图片并且缓存的operation)

并且SDWebImageCombineOperation遵循协议,所以operation可以作为返回值返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@interface SDWebImageCombinedOperation : NSObject @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end
@implementation SDWebImageCombinedOperation
- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    else {
        _cancelBlock = [cancelBlock copy];
    }
}
- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();
        // 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;
    }
}
@end

@synchronized是OC中一种方便地创建互斥锁的方式--它可以防止不同线程在同一时间执行区块的代码

self.failedURLs是一个NSSet类型的集合,里面存放的都是下载失败的图片的url,failedURLs不是NSArray类型的原因是:

在搜索一个个元素的时候NSSet比NSArray效率高,主要是它用到了一个算法hash(散列,哈希) ,比如你要存储A,一个hash算法直接就能找到A应该存储的位置;同样当你要访问A的时候,一个hash过程就能找到A存储的位置,对于NSArray,若想知道A到底在不在数组中,则需要遍历整个数据,显然效率较低了

并且NSSet里面不含有重复的元素,同一个下载失败的url只会存在一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (BOOL)containsObject:(ObjectType)anObject;,判断集合里面是否含有这个obj
    BOOL isFailedUrl = NO;
//创建一个互斥锁防止现有的别的线程修改failedURLs
//判断这个url是否是fail过的,如果url failed过的那么isFailedUrl就是true.
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
//如果url不存在那么直接返回一个block,如果url存在那么继续
//!(options & SDWebImageRetryFailed) 之前就提过一个类似的了,它的意思看这个options是不是和SDWebImageRetryFailed不相同
//如果不相同并且isFailedUrl是true.那么就回调一个error的block
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });
        return operation;
    }

把operation加入到self.runningOperations的数组里面,并创建一个互斥线程锁来保护这个操作

获取image的url对应的key

    @synchronized (self.runningOperations) {

        [self.runningOperations addObject:operation];

    }

    NSString *key = [self cacheKeyForURL:url];

其实看到这里,下面牵扯的代码就会越来越越多了,会牵扯到的类也越来越多,我们先一步步地顺着往下看,然后再看涉及到的类里面写了些什么,最后再做总结,在这之前如果你对NSOperation还不够了解建议你先暂时停下看看下这篇文章NSOperation。然后再继续往下阅读。

1
- (NSOperation *)queryDiskCacheForKey:(NSString *)key

done:(SDWebImageQueryCompletedBlock)doneBlock;是SDImageCache的一个方法,根据图片的key,异步查询磁盘缓存的方法

我们先来看下这个方法里面都有什么:

_ioQueue的定义是:@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;

_ioQueue的初始化是:

1
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

DISPATCH_QUEUE_SERIAL代表的是创建一个串行的队列,所以_ioQueue是一个串行队列(任务一个执行完毕才执行下一个)

PS:如果你对GCD队列不太了解可以先看下GCD使用经验与技巧浅谈,然后再继续阅读额

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
- (NSOperation *)queryDiskCacheForKey:(NSString *)key
done:(SDWebImageQueryCompletedBlock)doneBlock {
    if (!doneBlock) {
        return nil;
    }
    if (!key) {
        doneBlock(nil, SDImageCacheTypeNone);
        return nil;
    }
    // 1.首先查看内存缓存,如果查找到,则直接调用doneBlock并返回
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        doneBlock(image, SDImageCacheTypeMemory);
        return nil;
    }
//2.如果内存中没有,则在磁盘中查找,如果找到,则将其放到内存缓存中,并调用doneBlock回调
    NSOperation *operation = [NSOperation new];
//在ioQueue中串行处理所有磁盘缓存
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            return;
        }
//创建自动释放池,内存及时释放
        @autoreleasepool {
  //根据图片的url对应的key去磁盘缓存中查找图片
            UIImage *diskImage = [self diskImageForKey:key];
//如果可以在磁盘中查找到image,并且self.shouldCacheImagesInMemory = YES(默认是YES,if memory cache is enabled)就将image储存到内存缓存中
            if (diskImage && self.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
        //self.memCache是NSCache创建的一个对象,下面的方法是NSCache储存对象的方法,如果你对cost的作用不太了解可以看我另外一篇文章NSCache
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
  //最后在主线程里面调用doneBlock返回
            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, 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;
        }
//条件1:在缓存中没有找到图片或者options选项里面包含了SDWebImageRefreshCached(这两项都需要进行请求网络图片的)
//条件2:代理允许下载,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许
        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  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.创建一个组合Operation,是一个SDWebImageCombinedOperation对象,这个对象负责对下载operation创建和管理,同时有缓存功能,是对下载和缓存两个过程的组合。

2.先去寻找这张图片 内存缓存和磁盘缓存,这两个功能在self.imageCache的queryDiskCacheForKey: done:方法中完成,这个方法的返回值既是一个缓存operation,最终被赋给上面的Operation的cacheOperation属性。在查找缓存的完成回调中的代码是重点:它会根据是否设置了SDWebImageRefreshCached

选项和代理是否支持下载决定是否要进行下载,并对下载过程中遇到NSURLCache的情况做处理,还有下载失败的处理以及下载之后进行缓存,然后查看是否设置了形变选项并调用代理的形变方法进行对图片形变处理。

3.将上面的下载方法返回的操作命名为subOperation,并在组合操作operation的cancelBlock代码块中添加对subOperation的cancel方法的调用。

整个大体的操作流程,就是这些,你不感觉少点啥子麽,如何下载,如何缓存(缓存只是暂时看了两个相关的方法)我们在下一篇再详细地看。





---------------------------------------------------------------------- 华丽丽的分割线----------------------------------------------------------------------------------

SDWebImageDownloader

SDWebImageManager实现下载依赖于下载器:SDWebImageDownloader,下载器负责管理下载任务,而执行下载任务是由SDWebImageDownloaderOperation操作完成。

SDWebImageManager实现下载 就是调用下面这个方法:

- (id)downloadImageWithURL:(NSURL *)urloptions:(SDWebImageDownloaderOptions)optionsprogress:(SDWebImageDownloaderProgressBlock)progressBlockcompleted:(SDWebImageDownloaderCompletedBlock)completedBlock

我们还是先来看看SDWebImageDownloader里面都写了些什么:

SDWebImageDownloader.h

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {    //这个属于默认的使用模式了,前往下载,返回进度block信息,完成时调用completedBlock    SDWebImageDownloaderLowPriority = 1 << 0,       //渐进式下载 ,如果设置了这个选项,会在下载过程中,每次接收到一段返回数据就会调用一次完成回调,回调中的image参数为未下载完成的部分图像,可以实现将图片一点点显示出来的功能    SDWebImageDownloaderProgressiveDownload = 1 << 1,    /**     * 通常情况下request阻止使用NSURLCache.这个选项会默认使用NSURLCache     */    SDWebImageDownloaderUseNSURLCache = 1 << 2,    /**     *  如果从NSURLCache中读取图片,会在调用完成block的时候,传递空的image或者imageData     */    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,    /**     * 系统为iOS 4+时候,如果应用进入后台,继续下载.这个选项是为了实现在后台申请额外的时间来完成请求.如果后台任务到期,操作也会被取消     */    SDWebImageDownloaderContinueInBackground = 1 << 4,    /**     *  通过设置 NSMutableURLRequest.HTTPShouldHandleCookies = YES的方式来处理存储在NSHTTPCookieStore的cookies     */    SDWebImageDownloaderHandleCookies = 1 << 5,    /**     *  允许不受信任的SSL证书,在测试环境中很有用,在生产环境中要谨慎使用     */    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,    /**     * 将图片下载放到高优先级队列中     */    SDWebImageDownloaderHighPriority = 1 << 7,};

这些选项主要涉及到下载的优先级,缓存,后台任务执行,cookie处理以及证书认证几个方面,在创建下载操作的时候可以使用组合的选项来完成一些特殊的需求

定义里两个常量,后面通知的时候用的,这里的常量是全局常量

全局常量:不管你定义在任何文件夹,外部都能访问

const NSString *myName = @"杨千嬅染了红头发";

局部常量:用static修饰后,不能提供外界访问(只能在赋值的.m文件使用,外界不可访问)

static const NSString *myName= @"杨千嬅染了红头发";//官方也更推荐这样定义常量 而不是用#defineextern NSString *const SDWebImageDownloadStartNotification;extern NSString *const SDWebImageDownloadStopNotification;

定义了三个block

  • 第一个返回已经接收的图片数据的大小,未接收的图片数据的大小,- (void)sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress:completed:

  • 这个方法里面就有用到,因为图片的下载是需要时间的,所以这个block回调不止回调一次,会一直持续到图片完全下载或者下载失败才会停止回调

  • 第二个block回调 下载完成的图片 , 图片的数据 , 如果有error返回error ,以及下载是否完成的BOOl值

  • 第三个是header过滤:设置一个过滤器,为下载图片的HTTP request选取header.最终使用的headers是经过这个block过滤时候的返回值

typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);

定义的属性

/** * 解压已经下载缓存起来的图片可以提高性能,但是会消耗大量的内存 * 默认为YES显示比较高质量的图片,如果你遇到因内存消耗过多而造成崩溃的话可以设置为NO, */@property (assign, nonatomic) BOOL shouldDecompressImages;//下载队列最大的并发数,意思是队列中最多同时运行几条线程(全局搜索了一下,默认值是3)@property (assign, nonatomic) NSInteger maxConcurrentDownloads;/** * 当前在下载队列的操作总数,只读(这是一个瞬间值,因为只要一个操作下载完成就会移除下载队列) */@property (readonly, nonatomic) NSUInteger currentDownloadCount;/** *  下载操作的超时时间,默认是15s */@property (assign, nonatomic) NSTimeInterval downloadTimeout;/** *  枚举类型,代表着操作下载的顺序 */@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;//SDWebImageDownloaderExecutionOrder 的定义typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {    /**     * 默认值,所有的下载操作以队列类型(先进先出)执行     */    SDWebImageDownloaderFIFOExecutionOrder,    /**     * 所有的下载操作以栈类型(后进后出)执行     */    SDWebImageDownloaderLIFOExecutionOrder};/** *  SDWeImageDownloder是一个单例,这是初始化方法 */+ (SDWebImageDownloader *)sharedDownloader;/** *  为request操作设置默认的URL凭据,具体实施为:在将操作添加到队列之前,将操作的credential属性值设置为urlCredential */@property (strong, nonatomic) NSURLCredential *urlCredential;/** * Set username */@property (strong, nonatomic) NSString *username;/** * Set password */**局部常量**@property (strong, nonatomic) NSString *password;/** * 设置一个过滤器,为下载图片的HTTP request选取header.意味着最终使用的headers是经过这个block过滤之后的返回值。 */@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;

看完这些属性后我们在来看SDWebImageDownloader里面的两个核心方法,其他的方法会捎带说一下

第一个就是一开始我们说的,SDWebImageManager会调用的方法

- (id )downloadImageWithURL:(NSURL *)urloptions:(SDWebImageDownloaderOptions)optionsprogress:(SDWebImageDownloaderProgressBlock)progressBlockcompleted:(SDWebImageDownloaderCompletedBlock)completedBlock{    __block SDWebImageDownloaderOperation *operation;    __weak __typeof(self)wself = self;    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{    //这里面都是创建下载的回调}];}

先来看看-addProgressCallback:completedBlock:forURL:createCallback:里面都做了些什么

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.    //如果图片的url是空的就直接返回    if (url == nil) {        if (completedBlock != nil) {            completedBlock(nil, nil, nil, NO);        }        return;    }    dispatch_barrier_sync(self.barrierQueue, ^{        BOOL first = NO;        if (!self.URLCallbacks[url]) {            self.URLCallbacks[url] = [NSMutableArray new];            first = YES;        }        // Handle single download of simultaneous download request for the same URL        NSMutableArray *callbacksForURL = self.URLCallbacks[url];        NSMutableDictionary *callbacks = [NSMutableDictionary new];        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];        [callbacksForURL addObject:callbacks];        self.URLCallbacks[url] = callbacksForURL;        if (first) {            createCallback();        }    });}

下面重点也是不太好理解的东西,我也是又系统地复习了一下GCD,琢磨了有段时间才继续写的

dispatch_barrier_sync(self.barrierQueue, ^{BOOL first = NO; if (!self.URLCallbacks[url]) {self.URLCallbacks[url] = [NSMutableArray new]; first = YES;}

如果你GCD非常熟悉就跳过吧,不熟悉就先来看看我总结的GCD吧,写的比较好理解,先来看看 几个概念

Serial 串行 Concurrent并发

任务串行执行每次只有一个任务执行

任务并发执行就是同一时间可以有多个任务被执行

Synchronous 同步

一个同步函数只有在它完成预定的任务才返回(返回的意思是:返回当前线程,线程继续向下执行任务,你可以自己做个测试用一个同步函数,任务里面sleep(3);测试一下就明白了)

Asynchronous 异步

一个异步函数,会立即返回,预定任务会完成,但是不会等到这个任务完成才返回

Queues 队列

GCD提供 dispatch queues来处理代码,这些队列管理你提供给GCD的任务并用FIFO顺序执行,这保证了第一个被添加到队列里的任务会是队列中第一个执行的,第二个被添加的任务第二个开始执行,如此直到队列的终点

只能保证任务开始的顺序不能保证任务结束的顺序

Serial Queues 串行队列

串行队列的任务一次执行一个,每一个任务只有在前一个任务完成的时候才开始,但是你不知道一个任务(block)和下一个开始之间的时间长度

Concurrent Queues 并发队列

在并发队列中的任务能得到的保证是它们会被按照被添加的顺序开始执行,任务能以任意顺序完成,但是你不知道什么时候才开始运行下一个任务,或者任意时刻有多少block在运行,这完全取决于GCD

Queue Type 队列类型

主队列(main queue),和其它串行队列一样,这个队列中的任务一次只能执行一个,然后它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新UI的线程,这个队列就是用于发消息给UIView或发送通知的

全局调度队列(Global Dispatch Queues),它分了四种优先级(任务执行的优先级):background , low , default , high

Apple的API也会使用这些队列,所以你添加的任何任务都不会是这些队列唯一的任务

自己创建的串行队列 或者并发队列

GCD提供的函数

  • dispatch_async 异步 , 与其他线程无关

  • dispatch_sync 同步,阻塞其他线程

  • dispatch_apply 重复执行

  • dispatch_after 延迟执行

  • dispatch_barrier_async dispatch_barrier_sync(下面细讲)

只列举了一些常用的GCD函数,并不完全。

GCD的使用呢,总结起来就是先选用一个GCD提供的函数,传入一个你要调用的队列(三种队列类型的一种)和一个block(任务),队列会在轮到这个block执行的时候执行这个block。

注意:队列是用来存放任务的,队列并不等于线程,队列中存放的任务最后都要由线程来执行。

再回到刚才要看的部分,dispatch_barrier_sync是我们选用的GCD提供的函数,self.barrierQueue是存放任务的队列,block里面是要执行的任务

dispatch_barrier_sync(self.barrierQueue, ^{  BOOL first = NO;  if (!self.URLCallbacks[url]) {      self.URLCallbacks[url] = [NSMutableArray new];      first = YES;}

先来看看dispatch_barrier_sync

Dispatch Barrier解决多线程并发读写一个资源发生死锁

sync说明了这是个同步函数,任务不会立即返回,会等到任务执行结束才返回。

使用dispatch_barrier_sync此函数创建的任务会首先去查看队列中有没有别的任务要执行,如果有则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等到此方法中任务执行后才能执行,利用这个方法可以控制执行顺序。

Dispatch   Barrier确保提交的block是指定队列中特定时段唯一在执行的一个.在所有先于Dispatch Barrier的任务都完成的情况下这个block才开始执行.轮到这个block时barrier会执行这个block并且确保队列在此过程 不会执行其他任务.block完成后才恢复队列。

_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue",DISPATCH_QUEUE_CONCURRENT);

这是用户自己创建的队列,DISPATCH_QUEUE_CONCURRENT代表的是它是一个并行队列,为什么选择并发队列而不是串行队列我们来想一下:

串行队列可以保证任务按照添加的顺序一个个开始执行,并且上一个任务结束才开始下一个任务,这已经可以保证任务的执行顺序(或者说是任务结束的顺利)了,但是并行队列不一样,并发队列只能保证任务的开始,至于任务以什么样的顺序结束并不能保证但是并发队列使用Barrier却是可以保证的

这部分就先到这里继续向下看:

dispatch_barrier_sync(self.barrierQueue, ^{        BOOL first = NO;        if (!self.URLCallbacks[url]) {            self.URLCallbacks[url] = [NSMutableArray new];            first = YES;        } NSMutableArray *callbacksForURL = self.URLCallbacks[url];NSMutableDictionary *callbacks = [NSMutableDictionary new];if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; [callbacksForURL addObject:callbacks];self.URLCallbacks[url] = callbacksForURL;

URLCallbacks是一个可变字典,key是NSURL类型,value为NSMutableArray类型,value(数组里面)只包含一个元素,这个元素的类型是NSMutableDictionary类型,这个字典的key为NSString类型代表着回调类型,value为block,是对应的回调

这些代码的目的都是为了给url绑定回调

继续向下看:

if (first) {    createCallback();}

如果url第一次绑定它的回调,也就是第一次使用这个url创建下载任务则执行一次创建回调

在创建回调中 创建下载操作(下载操作并不是在这里创建的),dispatch_barrier_sync执行确保同一时间只有一个线程操作URLCallbacks属性,也就是确保了下面创建过程中在给operation传递回调的时候能取到正确的self.URLCallbacks[url]值,同事确保后面有相同的url再次创建的时候if (!self.URLCallbacks[url])分支不再进入,first==NO,也就不再继续调用创建回调,这样就确保了同一个url对应的图片不会重复下载

以上这部分代码总结起来只做了一件事情:在barrierQueue队列中创建下载任务

至此下载的任务都创建好了,下面该轮到下载的操作了:

- (id )downloadImageWithURL:(NSURL *)url                                         options:(SDWebImageDownloaderOptions)options                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock                                       completed:(SDWebImageDownloaderCompletedBlock)completedBlock {            __block SDWebImageDownloaderOperation *operation;            [self addProgressCallback:progressBlock                       completedBlock:completedBlock                               forURL:url                        createCallback:^{      //创建下载的回调,我们开始来看看创建完下载的回调之后里面都写了什么事情      //配置下载超时的时间         NSTimeInterval timeoutInterval = wself.downloadTimeout;       if (timeoutInterval == 0.0) {         timeoutInterval = 15.0;       }   /**         创建请求对象,并根据options参数设置其属性         为了避免潜在的重复缓存(NSURLCache + SDImageCache),        如果没有明确告知需要缓存,        则禁用图片请求的缓存操作, 这样就只有SDImageCache进行了缓存这里的options 是SDWebImageDownloaderOptions      */  NSMutableURLRequest *request =[[NSMutableURLRequest alloc] initWithURL:urlcachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData)timeoutInterval:timeoutInterval];// 通过设置 NSMutableURLRequest.HTTPShouldHandleCookies = YES//的方式来处理存储在NSHTTPCookieStore的cookies        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);//返回在接到上一个请求得得响应之前,饰扣需要传输数据,YES传输,NO不传输        request.HTTPShouldUsePipelining = YES;  }];};/**如果你自定义了wself.headersFilter,那就用你自己设置的wself.headersFilter来设置HTTP的header field它的定义是typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);一个返回结果为NSDictionary类型的block如果你没有自己设置wself.headersFilter那么就用SDWebImage提供的HTTPHeadersHTTPHeaders在#import "SDWebImageDownloader.h",init方法里面初始化,下载webp图片需要的header不一样(WebP格式,[谷歌]开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器带宽资源和数据空间)#ifdef SD_WEBP        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];#else        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];#endif*/  if (wself.headersFilter) {            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);        }        else {            request.allHTTPHeaderFields = wself.HTTPHeaders;        }/**创建SDWebImageDownLoaderOperation操作对象(下载的操作就是在SDWebImageDownLoaderOperation类里面进行的)传入了进度回调,完成回调,取消回调@property (assign, nonatomic) Class operationClass;将Class作为属性存储,初始化具体Class,使用的时候调用具体class的方法*/    operation = [[wself.operationClass alloc] initWithRequest:request                                                          options:options                                                         progress:^(NSInteger receivedSize, NSInteger expectedSize) { //progress block回调的操作                                                            SDWebImageDownloader *sself = wself;                                                             if (!sself) return;                                                             __block NSArray *callbacksForURL;  /**URLCallbacks是一个字典,key是url,value是一个数组,数组里面装的是字典,key是NSString代表着回调类型,value为block是对应的回调确保提交的block是指定队列中特定时段唯一在执行的一个.*/dispatch_sync(sself.barrierQueue, ^{            //根据key取出装了字典的数组                callbacksForURL = [sself.URLCallbacks[url] copy];        });  for (NSDictionary *callbacks in callbacksForURL) {       dispatch_async(dispatch_get_main_queue(), ^{  //根据kProgressCallbackKey这个key取出进度的操作      SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];      //返回已经接收的数据字节,以及未接收的数据(预计字节)     if (callback) callback(receivedSize, expectedSize);          });         }      }    completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {            //completed block 回调的操作                     SDWebImageDownloader *sself = wself;                     if (!sself) return;            //依旧是根据url这个key取出一个里面装了字典的数组                     __block NSArray *callbacksForURL;                     dispatch_barrier_sync(sself.barrierQueue, ^{                         callbacksForURL = [sself.URLCallbacks[url] copy];                       if (finished) {        //如果这个任务已经完成,就根据url这个key从URLCallbacks字典里面删除                                     [sself.URLCallbacks removeObjectForKey:url];                                        }                                         });         for (NSDictionary *callbacks in callbacksForURL) {  //根据kCompletedCallbackKey这个key取出SDWebImageDownloaderCompletedBlock(完成的block)                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];            //回调 图片 data error 是否完成的             if (callback) callback(image, data, error, finished);                                                            }                                                        }                                                        cancelled:^{            //将url对应的所有回调移除                                                SDWebImageDownloader *sself = wself;                                                            if (!sself) return;                                                            dispatch_barrier_async(sself.barrierQueue, ^{                                                                [sself.URLCallbacks removeObjectForKey:url];                                                            });                                                        }];//上面 是SDWebImageDownloaderOperation *operation的创建,从这里开始就都是对operation的配置 // 设置是否需要解压 operation.shouldDecompressImages = wself.shouldDecompressImages;/**用户认证 NSURLCredential当连接客户端与服务端进行数据传输的时候,web服务器收到客户端请求时可能需要先验证客户端是否是正常用户,再决定是否返回该接口的真实数据iOS7.0之前使用的网络框架是NSURLConnection,在 2013 的 WWDC 上,苹果推出了 NSURLConnection 的继任者:NSURLSessionSDWebImage使用的是NSURLConnection,这两种网络框架的认证调用的方法也是不一样的,有兴趣的可以去google一下这里只看下NSURLConnection的认证(在这里写看着有些吃力,移步到这个代码框外面阅读)*/        if (wself.urlCredential) {            operation.credential = wself.urlCredential;        } else if (wself.username && wself.password) {            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];        }//根据下载选项SDWebImageDownloaderHighPriority设置优先级        if (options & SDWebImageDownloaderHighPriority) {            operation.queuePriority = NSOperationQueuePriorityHigh;        } else if (options & SDWebImageDownloaderLowPriority) {            operation.queuePriority = NSOperationQueuePriorityLow;        }  //将下载操作加到下载队列中        [wself.downloadQueue addOperation:operation];        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {   /** 根据executionOrder设置操作的依赖关系  executionOrder代表着下载操作执行的顺序,它是一个枚举SD添加下载任务是同步的,而且都是在self.barrierQueue这个并行队列中,同步添加任务。这样也保证了根据executionOrder设置依赖关是正确的。换句话说如果创建下载任务不是使用dispatch_barrier_sync完成的,而是使用异步方法 ,虽然依次添加创建下载操作A、B、C的任务,但实际创建顺序可能为A、C、B,这样当executionOrder的值是SDWebImageDownloaderLIFOExecutionOrder,设置的操作依赖关系就变成了A依赖C,C依赖Btypedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {// 默认值,所有的下载操作以队列类型执行,先被加入下载队列的操作先执行SDWebImageDownloaderFIFOExecutionOrder,// 所有的下载操作以栈类型执行,后进先出,后被加入下载队列的操作先执行SDWebImageDownloaderLIFOExecutionOrder};*/            [wself.lastAddedOperation addDependency:operation];            wself.lastAddedOperation = operation;        }    }];    return operation;}

NSURLCredential 身份认证

认证过程:

1.web服务器接收到来自客户端的请求

2.web服务并不直接返回数据,而是要求客户端提供认证信息,也就是说挑战是服务端向客户端发起的

2.1要求客户端提供用户名与密码挑战 NSInternetPassword

2.2 要求客户端提供客户端证书 NSClientCertificate

2.3要求客户端信任该服务器

3.客户端回调执行,接收到需要提供认证信息,然后提供认证信息,并再次发送给web服务

4.web服务验证认证信息

4.1认证成功,将最终的数据结果发送给客户端

4.2认证失败,错误此次请求,返回错误码401

Web服务需要验证客户端网络请求

NSURLConnectionDelegate 提供的接收挑战,SDWeImage使用的就是这个方案

-(void)connection:(NSURLConnection *)connectionwillSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

至此下载管理 SDWebImageDownloader到这里就算结束了,它的主要作用就是创建下载任务,管理下载任务(取消,下载等状态改变)这里的重点就是对self.barrierQueue的理解,最后我们来看看SDWebImageDownloaderOptions下载操作和下载过程的实现

SDWebImageDownloaderOptions

它的作用就是网络请求的配置,进行网络请求以及数据处理

依旧先来看看它公开声明的属性和方法

@interface SDWebImageDownloaderOperation : NSOperation /** * 下载时用于网络请求的request */@property (strong, nonatomic, readonly) NSURLRequest *request;/** * 图片下载完成是否需要解压 */@property (assign, nonatomic) BOOL shouldDecompressImages;/** * :URLConnection是否需要咨询凭据仓库来对连接进行授权,默认YES */@property (nonatomic, assign) BOOL shouldUseCredentialStorage;/** * web服务要求客户端进行挑战,用NSURLConnectionDelegate提供的方法接收挑战,最终会生成一个挑战凭证,也是NSURLCredential的实例 credential */@property (nonatomic, strong) NSURLCredential *credential;/** *     SDWebImageDownloader.h里面定义的,一些下载相关的选项 */@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;/** * 预期的文件大小 */@property (assign, nonatomic) NSInteger expectedSize;/** * connection对象进行网络访问,接收到的response */@property (strong, nonatomic) NSURLResponse *response;/** *用默认的属性值初始化一个SDWebImageDownloaderOperation对象 */- (id)initWithRequest:(NSURLRequest *)request              options:(SDWebImageDownloaderOptions)options             progress:(SDWebImageDownloaderProgressBlock)progressBlock            completed:(SDWebImageDownloaderCompletedBlock)completedBlock            cancelled:(SDWebImageNoParamsBlock)cancelBlock;

然后继续看SDWebImageDownloaderOperation.h

初始化方法,这个就是初始化一个SDWebImageDownloaderOperation实例,没什么看点

- (id)initWithRequest:(NSURLRequest *)request              options:(SDWebImageDownloaderOptions)options             progress:(SDWebImageDownloaderProgressBlock)progressBlock            completed:(SDWebImageDownloaderCompletedBlock)completedBlock            cancelled:(SDWebImageNoParamsBlock)cancelBlock {    if ((self = [super init])) {        _request = request;        _shouldDecompressImages = YES;        _shouldUseCredentialStorage = YES;        _options = options;        _progressBlock = [progressBlock copy];        _completedBlock = [completedBlock copy];        _cancelBlock = [cancelBlock copy];        _executing = NO;        _finished = NO;        _expectedSize = 0;        responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called    }    return self;}

但是下面这个方法- (void)start就是关键了,它是对NSOperation- (void)start的重写,这个方法是执行下载任务的核心代码

- (void)start {  //先加一把线程锁,保证执行到这里的时候只有当前线程在执行下面的方法    @synchronized (self) {//如果下载操作被取消了        if (self.isCancelled) {            self.finished = YES;      //把下载相关的属性置为nil            [self reset];            return;        }#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0/**App 进入后台时,请求继续执行一段时间的方法,使用UIApplication的beginBackgroundTaskWithExpirationHandler方法向系统借用一点时间,继续执行下面的代码来完成connection的创建和进行下载任务。*/        Class UIApplicationClass = NSClassFromString(@"UIApplication");        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {            __weak __typeof__ (self) wself = self;            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{                __strong __typeof (wself) sself = wself;                if (sself) {      /**在后台任务执行时间超过最大时间时,          也就是后台任务过期执行过期回调。          在回调主动将这个后台任务结束。        */                    [sself cancel];                    [app endBackgroundTask:sself.backgroundTaskId];                    sself.backgroundTaskId = UIBackgroundTaskInvalid;                }            }];        }#endif        // 下载任务执行的状态,在执行是YES,不在执行时NO        self.executing = YES;      //创建用于下载的connection        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];     //获取当前得得线程        self.thread = [NSThread currentThread];    }    //开始下载    [self.connection start];    //如果connection创建完成    if (self.connection) {        if (self.progressBlock) {//任务开始立刻执行一次进度的回调            self.progressBlock(0, NSURLResponseUnknownLength);        }        dispatch_async(dispatch_get_main_queue(), ^{          //发送开始下载的通知            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];        });/**在 [self.connection start];有返回结果(正常完成,有错误都算是结果)之前,代码会一直阻塞在CFRunLoopRun()或者CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false) 这里,也就是说  [self.connection start];之后下载就一直在进行中,一直到下载完成或者出错了(这两种情况都会调用CFRunLoopStop),这个阻塞才会解除*/        if (floor(NSFoundationVersionNumber) = __IPHONE_4_0    Class UIApplicationClass = NSClassFromString(@"UIApplication");    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {        return;    }    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];        [app endBackgroundTask:self.backgroundTaskId];        self.backgroundTaskId = UIBackgroundTaskInvalid;    }#endif}

最后,我们来看NSURLConnection (delegate)

1)connection: didReceiveResponse:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {    //如果statusCode<400并且不等304    if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {        //设置文件的预期大小,如果response.expectedContentLength >0那么预期文件的大小就是response.expectedContentLength ,反之就是0        NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;        self.expectedSize = expected;        //立即完成一次进度回调        if (self.progressBlock) {            self.progressBlock(0, expected);        }         //初始化属性imageDate,用于拼接图片 二进制数据        self.imageData = [[NSMutableData alloc] initWithCapacity:expected];        self.response = response;        dispatch_async(dispatch_get_main_queue(), ^{        //异步的 向主队队列发送一个通知    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];        });    }    else {        NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];/**  如果 statusCode == 304 就调用[self cancelInternal]方法 ,或者取消self.connection的连接 取消操作,发送操作停止的通知,执行完成回调,停止当前的runloop,设置下载完成标记为YES,正在执行的为NO,将属性置为空*/        if (code == 304) {            [self cancelInternal];        } else {            [self.connection cancel];        }        dispatch_async(dispatch_get_main_queue(), ^{            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];        });        if (self.completedBlock) {            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);        }        CFRunLoopStop(CFRunLoopGetCurrent());        [self done];    }}

2)connection: didReceiveData拼接数据的协议

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {    [self.imageData appendData:data];    if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {        // 根据self.imageData获取已接收的数据的长度        const NSInteger totalSize = self.imageData.length;       /**        每次接收到数据时,都会用现有的数据创建一个CGImageSourceRef对象以做处理,        而且这个数据应该是已接收的全部数据,而不仅仅是新的字节,所以才使用self.imageData作为参数(注意创建imageSource使用的数据是CoreFoundation的data,但是self.imageData是NSData,所以用(__bridge CFDataRef)self.imageData做转化 )      */        CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);/**在首次接收到数据的时候,图片的长宽都是0(width+height == 0)先从这些包含图像信息的数据中取出图像的长,宽,方向等信息以备使用*/        if (width + height == 0) {           //获取图片的属性信息            CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);            if (properties) {                NSInteger orientationValue = -1;                  //图片像素的高度 可以前面加(__bridge NSNumber *)转换为NSNumber类型                CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);                if (val) CFNumberGetValue(val, kCFNumberLongType, &height);                //获取图片的宽度                val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);                if (val) CFNumberGetValue(val, kCFNumberLongType, &width);                //获取图片的朝向                val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);                if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);                //CoreFoundation对象类型不在ARC范围内,需要手动释放资源                CFRelease(properties);          /**                使用Core Craphics框架绘制image时,使用的是                initWithCGImage这个函数,但是使用这个函数有时候会造成图片朝向的错误,                所以在这里保存朝向信息,orientation是一个可以记录图片方向的枚举            */                orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];            }        }      /**        width+height>0 说明这时候已经接收到图片的数据了        totalSize < self.expectedSize 说明图片 还没有接收完全        */        if (width + height > 0 && totalSize < self.expectedSize) {            // 创建图片            CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);#ifdef TARGET_OS_IPHONE            // Workaround for iOS anamorphic(失真的 , 变形的) image            if (partialImageRef) {                const size_t partialHeight = CGImageGetHeight(partialImageRef);                CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();                CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);                CGColorSpaceRelease(colorSpace);                if (bmContext) {                    CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);                    CGImageRelease(partialImageRef);                    partialImageRef = CGBitmapContextCreateImage(bmContext);                    CGContextRelease(bmContext);                }                else {                    CGImageRelease(partialImageRef);                    partialImageRef = nil;                }            }#endif            if (partialImageRef) {                UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];                UIImage *scaledImage = [self scaledImageForKey:key image:image];                if (self.shouldDecompressImages) {                    image = [UIImage decodedImageWithImage:scaledImage];                }                else {                    image = scaledImage;                }                CGImageRelease(partialImageRef);                dispatch_main_sync_safe(^{                    if (self.completedBlock) {                        self.completedBlock(image, nil, nil, NO);                    }                });            }        }        CFRelease(imageSource);    }    if (self.progressBlock) {        self.progressBlock(self.imageData.length, self.expectedSize);    }}

3.connectionDidFinishLoading:这个方法完成以后,代理不再会接收人和connection发送的消息,标志着图片下载完成,一般下载任务正常结束之后就会执行一次这个方法

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;    @synchronized(self) {/**  停止当前的runLoop,将connection属性和thread属性发送下载停止的通知*/        CFRunLoopStop(CFRunLoopGetCurrent());        self.thread = nil;        self.connection = nil;        dispatch_async(dispatch_get_main_queue(), ^{            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];        });    }/**  检查sharedURLCache是否缓存了这次下载response如果没有就把responseFromCached设置为NO*/    if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {        responseFromCached = NO;    }    /**执行完成回调    */    if (completionBlock) {/**  图片的缓存用的都是SDWebCache,所以就算设置了SDWebImageDownloaderIgnoreCachedResponse,responseFromCached回调的图片也是nil(理解有可能有偏差)*/        if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {            completionBlock(nil, nil, nil, YES);        } else if (self.imageData) {  //将数据转换为UIImage类型            UIImage *image = [UIImage sd_imageWithData:self.imageData];            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];            image = [self scaledImageForKey:key image:image];            // 注意对于gif图片,不需要解压缩            if (!image.images) {                if (self.shouldDecompressImages) {                    image = [UIImage decodedImageWithImage:image];                }            }            if (CGSizeEqualToSize(image.size, CGSizeZero)) {    //如果图片的大小为0 , 完成回调报错                completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);            }            else {              //回调图片 已经图片的大小 完成状态YES                completionBlock(image, self.imageData, nil, YES);            }        } else {          //图片为空 回调 报错            completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);        }    }    self.completionBlock = nil;  //将NSConnection 设置为完成状态    [self done];}

到这里,看的也差不多了,认真看完感觉这个作者太厉害了,也真的学习到了很多,欢迎交流,也希望大家自己有空了也看一下,这次真的是拖了一个月因为有的东西我没明白就看了好多天也查了 各种资料,这次也算是尽力写好了吧,惭愧

包厢里的狂欢,曲终人散

have Fine

以上

原文出处:http://www.jianshu.com/p/82c7f2865c92





0 0
原创粉丝点击