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

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

(6)上一篇讲了下载图片的相关操作,下面我们看下在图片下载完层之后,回调相关block

 operation = [[wself.operationClass alloc] initWithRequest:request
                                                          options:options
                                                          progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                 SDWebImageDownloader *sself = wself;
                 if (!sself) return;
                 __block NSArray *callbacksForURL;
//                  barrierQueue是为了保证同一时刻只有一个线程对URLCallbacks进行操作
                 dispatch_sync(sself.barrierQueue, ^{
                     callbacksForURL = [sself.URLCallbacks[url] copy];
                 });
                 for (NSDictionary *callbacks in callbacksForURL) {
                     dispatch_async(dispatch_get_main_queue(), ^{
                         SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                         //有用户自定义block的话,就回调
                         if (callback) callback(receivedSize, expectedSize);
                     });
                 }
             }

这部分是进度条相关的block,
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
dispatch_sync(queue,block),同步,会阻塞当前线程,等到其后面的block执行完成之后,才继续执行其后的代码,也就保证了同一时刻只有一个线程能对URLCallbacks进行操作,跟在addProgressCallback方法中用dispatch_barrier_sync对URLCallbacks操作

 completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                AppLog(@"图片数据下载完成")
                SDWebImageDownloader *sself = wself;
                if (!sself) return;
                __block NSArray *callbacksForURL;
                //同步barrier任务将下载完成的url等操作从URLCallbacks从移除
                dispatch_barrier_sync(sself.barrierQueue, ^{
                    callbacksForURL = [sself.URLCallbacks[url] copy];
                    if (finished) {
                        [sself.URLCallbacks removeObjectForKey:url];
                    }
                });
                AppLog(@"completed callbacksForURL--->%@",callbacksForURL);
                for (NSDictionary *callbacks in callbacksForURL) {
                    SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                    //将刚下载完成的block
                    if (callback) callback(image, data, error, finished);
                }
            }

1、在SDWebImageDownloaderCompletedBlock中,下载图片是多线程异步下载的,但是在每个线程中图片下载完成后,使用的是dispatch_barrier_sync(queue,block),保证前面的操作完成之后,才能执行dispatch_barrier_sync中的block,dispatch_barrier_sync中的block执行完成之后,才能继续执行其后的代码。
2、该url下载操作完成之后,需要将该url对应的相关操作都移除 :[sself.URLCallbacks removeObjectForKey:url];
3、然后遍历,将对应下载成功的block,回调

取消的功能类似

cancelled:^{
                SDWebImageDownloader *sself = wself;
                if (!sself) return;
                dispatch_barrier_async(sself.barrierQueue, ^{
                    [sself.URLCallbacks removeObjectForKey:url];
                });
            }];

我们看下如下方法相关的回调

[self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished);

//operation不存在,或者取消,啥都不处理,因为如果我们调用completedBlock,可能会在另外的地方我们也调用了,或覆盖数据
__strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {

                }
                else if (error) {//下载失败
                    dispatch_main_sync_safe(^{
                        if (strongOperation && !strongOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, 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];
                        }
                    }
                }

下载完成的其他情况的相关block

AppLog(@"SDWebImageManager下载成功回调completedBlock");
if ((options & SDWebImageRetryFailed)) {
      @synchronized (self.failedURLs) {
             [self.failedURLs removeObject:url];
      }
}

如果设置了SDWebImageRetryFailed属性,需要将url从failedURLs中移除,以便可以重新下载

 BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    AppLog(@"是否缓存在磁盘:cacheOnDisk = %d",cacheOnDisk);
                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block图像刷新命中NSURLCache缓存,不要调用完成块
                    }

是否需要缓存在磁盘缓存中
若是图片命中NSURLCache缓存,不需要调用complete block

else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        AppLog(@"gif图像相关操作???");
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [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);
                                }
                            });
                        });
                    }

1、有下载的图像downloadedImage
2、(downloadedImage不是gif动图 || (设置了SDWebImageTransformAnimatedImage,要给它设置动画))
3、实现了imageManager:transformDownloadedImage:withURL:方法

else {

                        if (downloadedImage && finished) {//图像存在 且 下载完成
                            AppLog(@"图像存在 且 下载完成  将图像存储在磁盘");
                            //将图像存储在磁盘
                            [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);
                            }
                        });
                    }

图像存在 且 下载操作完成:将图片存储在磁盘
在主线程中回调completeBlock,显示图片

if (finished) {
                    @synchronized (self.runningOperations) {
                        if (strongOperation) {
                        //当执行完后,说明图片获取成功,可以把当前这个operation移除了。
                            [self.runningOperations removeObject:strongOperation];
                        }
                    }
                }

下载操作完成,加锁self.runningOperations
当执行完后,说明图片获取成功,可以把当前这个operation移除了。

看下将图像存储在缓存中的方法
- (void)storeImage:(UIImage )image recalculateFromImage:(BOOL)recalculate imageData:(NSData )imageData forKey:(NSString )key toDisk:(BOOL)toDisk;*

if (!image || !key) {
        return;
    }
    // if memory cache is enabled
    if (self.shouldCacheImagesInMemory) {
        AppLog(@"将图像存储在内存中……");
        NSUInteger cost = SDCacheCostForImage(image);//计算图片存储的大小
        [self.memCache setObject:image forKey:key cost:cost];//将图像存储在内存中
    }

1、图片不存在 || key不存在,return
2、是否使用内存缓存,默认YES,将图片存储在内存缓存

开启新线程,异步将图片缓存在磁盘中

dispatch_async(self.ioQueue, ^{
            NSData *data = imageData;
            //recalculate: 表示是否可以使用imageData,或者是否应该从UIImage构造新数据
             //imageData为空(下载的图像若是需要transform,那么imageData就会为空)
            if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
                //如果imageData为零(即,如果试图直接保存UIImage或在下载时转换图像)并且图像具有alpha通道,我们会认为它是PNG以避免丢失透明度
                int alphaInfo = CGImageGetAlphaInfo(image.CGImage);//图像透明度
                BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                                  alphaInfo == kCGImageAlphaNoneSkipFirst ||
                                  alphaInfo == kCGImageAlphaNoneSkipLast);
                BOOL imageIsPng = hasAlpha;

                // But if we have an image data, we will look at the preffix
                //PNG文件的前八个字节总是包含一样的签名
                if ([imageData length] >= [kPNGSignatureData length]) {
                    imageIsPng = ImageDataHasPNGPreffix(imageData);
                }

                //image是png
                if (imageIsPng) {
                    data = UIImagePNGRepresentation(image);
                }
                else {//image是jpeg,压缩质量为1,无压缩
                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
                }
#else
//其他平台,不再iPhone上,使用如下方法
                data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
            }

若是图片数据处理之后,还存在,将图片缓存在磁盘

 if (data) {
                if (![_fileManager fileExistsAtPath:_diskCachePath]) {//检查路径是否存在
                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
                }

                // get cache Path for image key
                NSString *cachePathForKey = [self defaultCachePathForKey:key];
                AppLog(@"cachePathForKey-->%@",cachePathForKey);
                // transform to NSUrl
                NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
                AppLog(@"fileURL-->%@",fileURL);
                [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];//存储图片数据

                // disable iCloud backup
               if (self.shouldDisableiCloud) {//iCloud 备份,默认禁用icloud备份
                    [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
                }
            }

1、cachePathForKey:
/Users/xxx/Library/Developer/CoreSimulator/Devices/483F34CE-F906-4FE5-BD3A-A758DCEEB118/data/Containers/Data/Application/69141B68-DCDB-4991-A07A-0592290C5F63/Library/Caches/default/com.hackemist.SDWebImageCache.default/4ad9ae8eabfec60b40bf48f0bfc2d120.png
2、fileURL:
file:///Users/xxx/Library/Developer/CoreSimulator/Devices/483F34CE-F906-4FE5-BD3A-A758DCEEB118/data/Containers/Data/Application/69141B68-DCDB-4991-A07A-0592290C5F63/Library/Caches/default/com.hackemist.SDWebImageCache.default/4ad9ae8eabfec60b40bf48f0bfc2d120.png

(7)以上是将图片缓存在内存缓存和磁盘缓存中,以下看下图片是如何清除的
1、清除内存缓存

//清除内存缓存
- (void)clearMemory {
    [self.memCache removeAllObjects];
}

2、清除磁盘缓存

//清除磁盘缓存
- (void)clearDisk {
    [self clearDiskOnCompletion:nil];
}
//清除所有磁盘缓存的图像。 非阻塞方法 - 立即返回。
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion
{
    dispatch_async(self.ioQueue, ^{
        //移除diskCachePath文件夹中的数据
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        //重新创建缓存的路径
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];

        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

3、从磁盘中删除所有过期的缓存图像。

// 从磁盘中删除所有过期的缓存图像。
- (void)cleanDisk {
    [self cleanDiskWithCompletionBlock:nil];
}
// 从磁盘中删除所有过期的缓存图像。 非阻塞方法 - 立即返回。
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
        /*
         NSURLIsDirectoryKey:判断遍历到的URL所指对象是否是目录.
         NSURLContentModificationDateKey:返回遍历到项目内容的最后存取日期.
         NSURLTotalFileAllocatedSizeKey:文件总的分配大小。以字节为单位的文件总分配大小(这可能包括元数据使用的空间),或者如果不可用,则为零。 如果资源被压缩,这可能小于NSURLTotalFileSizeKey返回的值。
         */

        //NSDirectoryEnumerationSkipsHiddenFiles:表示不遍历隐藏文件
        // This enumerator prefetches useful properties for our cache files.此枚举器为我们的缓存文件预取有用的属性。
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];

        //过期时间,当前时间 - 最大缓存时间(默认是一周)
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;

        // Enumerate all of the files in the cache directory.  This loop has two purposes:
        //
        //  1. Removing files that are older than the expiration date.
        //  2. Storing file attributes for the size-based cleanup pass.
        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            //resourceValuesForKeys:获取指向的文件信息
            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];

            // Skip directories.跳过跟目录
            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            // Remove files that are older than the expiration date;获取最近修改日期
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }

            // Store a reference to this file and account for its total size.存储对此文件的引用并考虑其总大小。
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }

        for (NSURL *fileURL in urlsToDelete) {//遍历过期的url
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        // If our remaining disk cache exceeds a configured maximum size, perform a second
        // size-based cleanup pass.  We delete the oldest files first. 如果我们的剩余磁盘缓存超过配置的最大大小,请执行第二次基于大小的清理通行证。 我们先删除最早的文件。
        //我们当前未过期的文件大小 > 我们配置缓存文件大小 ,则删除日期最早的文件,将此清理过程的最大缓存大小的一半作为目标。
        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;//将此清理过程的最大缓存大小的一半作为目标。

            // Sort the remaining cache files by their last modification time (oldest first).
            //按剩余缓存文件的上次修改时间排序(最早的第一个)。
            NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                            usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                            }];

            // Delete files until we fall below our desired cache size.删除文件,直到我们低于我们所需的缓存大小。
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

4、同步删除内存和磁盘缓存中的映像

//同步删除内存和磁盘缓存中的映像
- (void)removeImageForKey:(NSString *)key {
    [self removeImageForKey:key withCompletion:nil];
}

- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion {
    [self removeImageForKey:key fromDisk:YES withCompletion:completion];
}

- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk {
    [self removeImageForKey:key fromDisk:fromDisk withCompletion:nil];
}

- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {

    if (key == nil) {
        return;
    }

    //如果允许缓存在内存,需要将内存缓存中的image移除
    if (self.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    //如果要删除磁盘缓存中的image
    if (fromDisk) {
        dispatch_async(self.ioQueue, ^{
            //移除磁盘缓存的路径
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];

            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }

}

猜你喜欢

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