美文网首页
SDImageCache

SDImageCache

作者: 认不出我来 | 来源:发表于2018-06-07 11:20 被阅读0次

    .h文件

    /**
     * 目前缓存里没有.
     */
    SDImageCacheTypeNone,
    /**
     * 磁盘缓存.
     */
    SDImageCacheTypeDisk,
    /**
     * 内存缓存.
     */
    SDImageCacheTypeMemory
    

    // 查询缓存图片完成的回调block
    typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);

    // 查询某张图片是否被缓存的回调block
    typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);

    // 计算disk上所有缓存文件的个数和总大小
    typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);

    磁盘缓存的写操作是异步的,不会导致UI交互上的延迟

    /**

    • 缓存配置
      */
      @property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;

    /**

    • 最大的内存缓存的开销
      */
      @property (assign, nonatomic) NSUInteger maxMemoryCost;

    /**

    • 最大的内存缓存的数量限制.
      */
      @property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

    方法名大家看头文件都很好懂,没有生硬的单词。

    .m文件

    AutoPurgeCache类继承NSCache,用此类创建内存缓存。
    在收到内存警告的通知后,自动清除内存缓存。在该类里不需要再写removeAllObjects,因为在父类NSCache里已经提供了。

    // 内联函数,算出一个UIImage的缓存开销

    FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
    #if SD_MAC
        return image.size.height * image.size.width;
    #elif SD_UIKIT || SD_WATCH
        return image.size.height * image.size.width * image.scale * image.scale;
    #endif
    }
    

    nonnull修饰的属性表示不能传空,nullable修饰的属性表示可以传空。

    默认创建的单例命名空间“default”

    获取SD磁盘缓存的路径 Library/Caches/default/com.hackemist.SDWebImageCache.default/

    - (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
        NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        return [paths[0] stringByAppendingPathComponent:fullNamespace];
    }
    

    初始化做了以下操作

    1.创建了一个io操作的串行队列 
    2.创建缓存配置文件、内存缓存实例
    3.生成磁盘缓存的路径
    4.创建_fileManager实例,后续对_fileManager的所有操作都在ioQueue中执行
    5.监听一些通知,用来清除内存缓存或者删除磁盘上一些旧的文件。
    

    检查当前队列是否是io队列

    - (void)checkIfQueueIsIOQueue {
        const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
        const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
        if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
            NSLog(@"This method should be called from the ioQueue");
        }
    }
    

    缓存文件名生成规则:对图片url的utf8String进行md5,链接最后如果有扩展名会拼接上扩展名

    - (nullable NSString *)cachedFileNameForKey:(nullable 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]];
    
        return filename;
    }
    

    从网络上下好一张图片,缓存图片的时候,先判断是否开启了内存缓存,默认是开启的,开启的话,先存到内存里,直接存UIImage对象。
    然后判断是否需要存到磁盘上,如果需要,把图片的NSData数据存放到磁盘上。

    - (void)storeImage:(nullable UIImage *)image
             imageData:(nullable NSData *)imageData
                forKey:(nullable NSString *)key
                toDisk:(BOOL)toDisk
            completion:(nullable SDWebImageNoParamsBlock)completionBlock {
        if (!image || !key) {
            if (completionBlock) {
                completionBlock();
            }
            return;
        }
        // if memory cache is enabled
        if (self.config.shouldCacheImagesInMemory) {
            NSUInteger cost = SDCacheCostForImage(image);
            [self.memCache setObject:image forKey:key cost:cost];
        }
        
        if (toDisk) {
            dispatch_async(self.ioQueue, ^{
                @autoreleasepool {
                    NSData *data = imageData;
                    if (!data && image) {
                        SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
                        data = [image sd_imageDataAsFormat:imageFormatFromData];
                    }                
                    [self storeImageDataToDisk:data forKey:key];
                }
                
                if (completionBlock) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completionBlock();
                    });
                }
            });
        } else {
            if (completionBlock) {
                completionBlock();
            }
        }
    }
    

    这个方法就是缓存到磁盘上,重点讲一下 [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];这句,这句的意思是防止这个本地url所对应的文件被备份。

    - (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
        if (!imageData || !key) {
            return;
        }
        
        [self checkIfQueueIsIOQueue];
        
        if (![_fileManager fileExistsAtPath:_diskCachePath]) {
            [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
        }
        
        // get cache Path for image key
        NSString *cachePathForKey = [self defaultCachePathForKey:key];
        // transform to NSUrl
        NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
        
        [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
        
        // disable iCloud backup
        if (self.config.shouldDisableiCloud) {
            [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
        }
    }
    

    检查磁盘上是否有该key对应的缓存

    - (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
        dispatch_async(_ioQueue, ^{
            BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
    
            // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
            // checking the key with and without the extension
            if (!exists) {
                exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
            }
    
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock(exists);
                });
            }
        });
    }
    

    取图片缓存的时候,先取内存缓存,取不到,再取磁盘缓存,所以执行该方法说明已经找过内存缓存了,没有找到,所以如果从磁盘上能取到该缓存图片,说明可能是以前没开启内存缓存功能,所以在磁盘上取到了之后,判断现在是开启内存缓存的状态的情况下,需要再往内存中存一份。

    - (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
        UIImage *diskImage = [self diskImageForKey:key];
        if (diskImage && self.config.shouldCacheImagesInMemory) {
            NSUInteger cost = SDCacheCostForImage(diskImage);
            [self.memCache setObject:diskImage forKey:key cost:cost];
        }
    
        return diskImage;
    }
    

    从所有的可能的磁盘缓存路径上进行查找

    - (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
        NSString *defaultPath = [self defaultCachePathForKey:key];
        NSData *data = [NSData dataWithContentsOfFile:defaultPath];
        if (data) {
            return data;
        }
    
        // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
        // checking the key with and without the extension
        data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension];
        if (data) {
            return data;
        }
    
        NSArray<NSString *> *customPaths = [self.customPaths copy];
        for (NSString *path in customPaths) {
            NSString *filePath = [self cachePathForKey:key inPath:path];
            NSData *imageData = [NSData dataWithContentsOfFile:filePath];
            if (imageData) {
                return imageData;
            }
    
            // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
            // checking the key with and without the extension
            imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension];
            if (imageData) {
                return imageData;
            }
        }
    
        return nil;
    }
    

    转化成对应scale的图片

    - (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
        return SDScaledImageForKey(key, image);
    }
    

    按照key值进行缓存查询的操作,返回operation对象,便于后续可以进行操作的cancel操作等。

    - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
        if (!key) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return nil;
        }
    
        // First check the in-memory cache...
        UIImage *image = [self imageFromMemoryCacheForKey:key];
        if (image) {
            NSData *diskData = nil;
            if ([image isGIF]) {
                diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            }
            if (doneBlock) {
                doneBlock(image, diskData, SDImageCacheTypeMemory);
            }
            return nil;
        }
    
        NSOperation *operation = [NSOperation new];
        dispatch_async(self.ioQueue, ^{
            if (operation.isCancelled) {
                // do not call the completion if cancelled
                return;
            }
    
            @autoreleasepool {
                NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
                UIImage *diskImage = [self diskImageForKey:key];
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = SDCacheCostForImage(diskImage);
                    [self.memCache setObject:diskImage forKey:key cost:cost];
                }
    
                if (doneBlock) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                    });
                }
            }
        });
    
        return operation;
    }
    

    删除旧文件的操作,删除最后修改时间是7天前的,SD默认的最大缓存时间是7天,然后判断如果删除后剩余的磁盘缓存大于允许缓存的最大开销,那么设置最大开销的一半做为阈值执行删除操作。

    - (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
        dispatch_async(self.ioQueue, ^{
            NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
            NSArray<NSString *> *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.config.maxCacheAge];
            NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *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<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
            for (NSURL *fileURL in fileEnumerator) {
                NSError *error;
                NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
    
                // Skip directories and errors.
                if (error || !resourceValues || [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[fileURL] = resourceValues;
            }
            
            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.
            if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
                // Target half of our maximum cache size for this cleanup pass.
                const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
    
                // Sort the remaining cache files by their last modification time (oldest first).
                NSArray<NSURL *> *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<NSString *, id> *resourceValues = cacheFiles[fileURL];
                        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                        currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
    
                        if (currentCacheSize < desiredCacheSize) {
                            break;
                        }
                    }
                }
            }
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    }
    

    这个是应用程序在收到进入后台通知后的处理方法,首先在进入后台后申请更多的时间进行操作,用到beginBackgroundTaskWithExpirationHandler。

    - (void)backgroundDeleteOldFiles {
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
            return;
        }
        UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
        __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
            // Clean up any unfinished task business by marking where you
            // stopped or ending the task outright.
            [application endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];
    
        // Start the long-running task and return immediately.
        [self deleteOldFilesWithCompletionBlock:^{
            [application endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];
    }
    

    相关文章

      网友评论

          本文标题:SDImageCache

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