SDWebImage源码学习篇(一)

SDImageCache

  1. [UIScreen mainScreen].scale

    开始也以为是屏幕缩放,其实是判断屏幕分辨率的方法。
    其值为1、2、3时,分别对应@1x、@2x、@3x的图片。

  2. __nullable__nonnull

    这两个关键字之前就稍有接触。是苹果为了兼容OC与Swift混编时加入
    的新特性。以区别是否Swift中的Option。具体戳这里:Objective-C新特性__nonnull和__nullable

  3. SDImageCache单例中维护着一个缓存集合NSCache,用以管理从Disk加载到内存的图片缓存。
    简单介绍请看掘金的NSCache

    1.NSCache,与NSMutableDictionary的用法类似,但它是线程安全的,不需要加线程锁。
    2.NSCache具有自动删除的功能,以减少系统占用的内存。
    3.其对象不会被复制,键不需要实现 NSCopying 协议。
    

    当收到内存警告通知UIApplicationDidReceiveMemoryWarningNotification时,
    NSCache清理所有对象。

    当收到通知UIApplicationWillTerminateNotification程序被杀死时,清理沙盒下的缓 存。

    当进入后台时UIApplicationDidEnterBackgroundNotification,使用 UIBackgroundTaskIdentifier在后台再运行一段时间,来处理过期的缓存图片。


  4. 通过比较图片Data形式十六进制,前8位Bytes,判断图片为PNG/JPG。SDWebImage中实现的方 法为BOOL ImageDataHasPNGPreffix(NSData *data);

    // PNG signature bytes and data (below)
    static unsigned char kPNGSignatureBytes[8] = 
    {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
    
  5. 创建一个对象ioQueue名为"com.hackemist.SDWebImageCache"的线程队列,串行队列按照FIFO顺序执行。一些NSFileManager有关的操作在这个线程中进行。

  6. SDWebImage的图片在缓存中的默认时间是一个星期。 可自行设置maxCacheAge

    static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; 
    // 1 week
    
  7. 计算图片的字节数大小,并设置给NSCache的缓存中。存储图片到Memery的同时会重新转化成Data(判断JPG或PNG格式),然后使用FileManager存到相应的Disk路径中。值得注意一下的是SDCacheCostForImage

    if (self.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    
  8. 查询图片是否存在于硬盘Disk中,Block返回存在的标志,在ioQueue线程中操作。

  - (void)diskImageExistsWithKey:(NSString *)key 
  completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
  1. SDImageCache查询图片的逻辑是先从NSCache中查询,如果有则返回图片Image,没有则从Disk中查询(查询目录包括自定义的目录),如果有就返回Image并把Image加载到Memory中。即第7点中所讲。

    返回Image的过程中还进行了图片的处理,还原其分辨率格式(@2x、@3x、gif、webp)、以及减压缩decodedImageWithImage,而这个提示会有内存暴增警告需要注意。

  2. 以下方法理解起来有点小迷糊:

```
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock 

//以下为方法中的部分实现
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
    if (operation.isCancelled) {
        return;
    }

    @autoreleasepool {
        //Disk中查询Image

        dispatch_async(dispatch_get_main_queue(), ^{
            doneBlock(diskImage, SDImageCacheTypeDisk);
        });
    }
});

```
它返回的是个NSOperation操作,其实是查询Image的存在方式(缓存中 Or Disk Or Nil),从而策略性的决定是否Image需要在网络下载。网上说的意思是`从磁盘或者内存查询的过程是异步的,后面可能需要cancel,所以这样做`
  1. SDImageCache提供了两个方法clearDiskcleanDisk,分别是清空整个图片缓存目录、清空过期的缓存图片。
    - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        
        //设置要获取的文件的信息:是否为文件、最后修改日期、全部文件所占的大小
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

        // 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) {
            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用来继续比较时间,删除最之前的图片缓存以满足所设置的缓存需求
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }
        
        
        for (NSURL *fileURL in urlsToDelete) {
            [_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.
        // 计算出的缓存大小不满足设置的CacheSize,作相应删除处理
        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];
                    
                    //删除文件时继续计算当前缓存大小,直至满足预定的大小才Break。
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

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

这个删除缓存图片的逻辑处理得很巧妙,在实际工作中值得我们借鉴,
像我们现在的IM中删除指定时间的聊天记录等等。

  1. 可以使用NSDirectoryEnumerator去检索沙盒文件,处理计算文件大小、文件数量等。具体使用介绍看苹果官方API文档 DirectoryEnumerator

你可能感兴趣的:(SDWebImage源码学习篇(一))