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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容