IOS SDWebImage 2.X源码阅读(二)

前言:
IOS SDWebImage 2.X源码阅读(一)
IOS SDWebImage 2.X源码阅读(二)
IOS SDWebImage 2.X源码阅读(三)
IOS SDWebImage 2.X源码阅读(四)

(3)、继续上篇博文,我们先看下它是如何从缓存中查找图片的
调用SDImageCache类中的

–(NSOperation*)queryDiskCacheForKey:(NSString*)key done:(SDWebImageQueryCompletedBlock)doneBlock;

一开始是一些判断

 if (!doneBlock) {
        return nil;
    }
    if (!key) {
        doneBlock(nil, SDImageCacheTypeNone);
        return nil;
    }

先从内存缓存中开始查找

 // First check the in-memory cache...
    //查寻内存缓存。即先从内存缓存中查找
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        AppLog(@"从内存缓存查找到图片………………");
        doneBlock(image, SDImageCacheTypeMemory);
        return nil;
    }

内存缓存查找方法调用只有一句代码

//异步查寻内存缓存。
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
    return [self.memCache objectForKey:key];
}

在内存中没有查找到时,会另起一个异步线程从内存中查找,不会阻塞主线程

NSOperation *operation = [NSOperation new];
    //开启一个异步操作
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            return;
        }

         //在自动释放池中查找磁盘缓存,为啥呢??--》如果你的应用程序或者线程是要长期运行的并且有可能产生大量autoreleased对象, 你应该使用autorelease pool blocks 
        @autoreleasepool {
            UIImage *diskImage = [self diskImageForKey:key];//从磁盘缓存中查找
            if (diskImage && self.shouldCacheImagesInMemory) {//图片存在 & 可以在内存中缓存
                NSUInteger cost = SDCacheCostForImage(diskImage);//图片大小
                [self.memCache setObject:diskImage forKey:key cost:cost];//将图片从磁盘缓存中复制到内存中
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, SDImageCacheTypeDisk);
            });
        }
    });

我们看下dispatch_async它是一个异步任务,再看下self.ioQueue是个串行队列

//创建io 串行队列
_ioQueue = dispatch_queue_create(“com.hackemist.SDWebImageCache”,
DISPATCH_QUEUE_SERIAL);

关于dispatch_async和dispatch_sync的相关内容,请参考我的这篇博客由此可知,是串行队列+异步任务 :会开启新的线程,任务逐个执行

看下- (UIImage )diskImageForKey:(NSString )key 方法中的代码,根绝key就是图片的url,进去磁盘缓存查找

UIImage *diskImage = [self diskImageForKey:key];//从磁盘缓存中查找

在该方法中又调用了diskImageForKey方法,看下它里面的代码,首先第一行代码

NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
调用了diskImageDataBySearchingAllPathsForKey方法,根据key搜索存储在磁盘中的完整路径,后面看到代码,我们知道它是对key进行md5加密、拼接等操作
//获取某个键的缓存路径
- (NSString )defaultCachePathForKey:(NSString )key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}

我们查询下self.diskCachePath是怎么拼接的呢

//(一)、
- (id)init {
    return [self initWithNamespace:@"default"];
}
//(二)用特定的名称空间初始化一个新的缓存存储
- (id)initWithNamespace:(NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}
//(三)根据命名空间初始化磁盘缓存
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
//(四)用特定的名称空间和目录初始化一个新的缓存存储
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
    if ((self = [super init])) {
        AppLog(@"初始化磁盘存储空间")
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

// Init the disk cache
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }
        //………………………………………………………………
        }
    }

上面就是缓存路径的跟路径了,还要跟key进行md5加密拼接

//获取某个键的缓存路径(需要缓存路径根文件夹)
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
    NSString *filename = [self cachedFileNameForKey:key];////对key进行MD5 处理,生成的文件名
    return [path stringByAppendingPathComponent:filename];
}

//对key进行MD5 处理,生成的文件名
- (NSString *)cachedFileNameForKey:(NSString *)key {
    const char *str = [key UTF8String];
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];

    AppLog(@"md5加密的filename-->%@",filename);
    return filename;
}

以上就是从磁盘缓存中查找数据,我们看下查找完成后,是如何处理的,回到刚才的- (UIImage )diskImageForKey:(NSString )key方法中

if (data) {
        AppLog(@"在磁盘缓存中找到图片");
        UIImage *image = [UIImage sd_imageWithData:data];
        image = [self scaledImageForKey:key image:image];//缩放图像
        if (self.shouldDecompressImages) {//解压下载
            AppLog(@"在磁盘中找到图片 -- 解压下载");
            image = [UIImage decodedImageWithImage:image];
        }
        return image;
    }
    else {
        return nil;
    }

再次回到异步线程查询的代码中去,在磁盘查找到数据,可以将数据复制到内存中去

 UIImage *diskImage = [self diskImageForKey:key];//从磁盘缓存中查找
            if (diskImage && self.shouldCacheImagesInMemory) {//图片存在 & 可以在内存中缓存
                NSUInteger cost = SDCacheCostForImage(diskImage);//图片大小
                [self.memCache setObject:diskImage forKey:key cost:cost];//将图片从磁盘缓存中复制到内存中
            }

以上就是整个完整的查找过程了,下面看下没找到图片,是如何去下载的

(4)、返回去看下SDWebImageManager类中
downloadImageWithURL:options:progress:completed:这个方法中说到,若是从缓存中未查找到图片,那么需要去下载

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;
……………………以上部分,设置downloaderOptions与options一致…………
if (image && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
// 如果图像已经缓存但强制刷新,强制逐渐关闭
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing 如果图像缓存但强制刷新,则忽略从NSURLCache读取的图像
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
注意这部分是从缓存中查找到了image,也设置了SDWebImageRefreshCached,需要关闭SDWebImageDownloaderProgressiveDownload,忽略从NSURLCache缓存中获取的iamge

然后又调用SDWebImageDownloader类中的方法,以为可以看到真正下载图片的方法,然而还木有………………

– (id )downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;

在该方法中有

 __block SDWebImageDownloaderOperation *operation;
 __weak __typeof(self)wself = self;

然后直接调用了该类的如下方法

– (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(NSURL *)url
createCallback:(SDWebImageNoParamsBlock)createCallback
看下该方法中的代码

 //该URL将被用作回调字典的关键字,因此它不能为零。 如果没有图像或数据,立即调用完成的块。
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);//(no image , no data, error, no finished)
        }
        return;
    }

我们看到下面后有self.URLCallbacks,看到定义它是个NSMutableDictionary那么它存储的内容是什么呢

//1、首先它有个ulr为关键字的数组,“url1”:[]
 self.URLCallbacks[url] = [NSMutableArray new];
 /*2、URL包含如下两个的字典
     progress :progressBlock
     completed:completedBlock
 */
  NSMutableDictionary *callbacks = [NSMutableDictionary new];
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
        [callbacksForURL addObject:callbacks];
 self.URLCallbacks[url] = callbacksForURL;
 //3、self.URLCallbacks的包含内容如下
 {
     "http://xxx/img1.jpg" = ({
            progress = "<progressBlock1>"
            completed = "<completedBlock2>"
         },{ 
             progress = "<progressBlock1>"
             completed = "<completedBlock2>"}
             ......
         ),
    "http://xxx/img2.jpg" = ({
            progress = "<progressBlock1>"
            completed = "<completedBlock2>"
         },{ 
             progress = "<progressBlock1>"
             completed = "<completedBlock2>"}
             ......
         )
 }

我们看到用到了dispatch_barrier_sync,它是等待前面任务执行完,在执行自己的任务,并且阻碍它后面block任务执行,直到它的block执行完成,才能执行后面的代码。在这部分,它将对self.URLCallbacks操作放进了dispatch_barrier_sync为啥呢?

因为允许多张图片同时下载,那么在对self.URLCallbacks操作过程中要保证一一对应,dispatch_barrier_sync来保证同一时间内只有一个线程在操作URLCallbacks,在此处对它的操作完成后,才能继续其他的操作

注意:

if (first) {//第一次未加载  去下载图片
    createCallback();//这个回调去下载图片
 }

回到这个方法中去

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock;

设置默认下载时间

NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }

猜你喜欢

转载自blog.csdn.net/ycf03211230/article/details/79559916