SDWebImage源码解析

SDWebImage中核心类如下所示:

26E06FD9-3B94-4AB4-9375-A2E7E5CC4470副本.png

一、SDImageCache
SDImageCache负责缓存图片,包含几个对外属性

//是否解压图片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//iCloud 备份
@property (assign, nonatomic) BOOL shouldDisableiCloud;
//是否能够缓存图片到内存
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
//最大内存使用,内存
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//缓存对象的数量最大值
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
//缓存时间,默认一周
@property (assign, nonatomic) NSInteger maxCacheAge;
//缓存最大值,磁盘
@property (assign, nonatomic) NSUInteger maxCacheSize;

内部属性:

//缓存对象
@property (strong, nonatomic) NSCache *memCache;
//磁盘缓存路径
@property (strong, nonatomic) NSString *diskCachePath;
//自定义路径数组,如bundle里的图片,可以添加到该数组中
@property (strong, nonatomic) NSMutableArray *customPaths;
//读写队列
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;

AutoPurgeCache,当收到内存警告时自动清空缓存

//初始化类的成员变量,监听内存警告、APP退到后台、APP终止通知

  • (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory
    //根据key值、path值生成文件路径
  • (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path
    //根据key值得到文件名
  • (NSString *)cachedFileNameForKey:(NSString )key
    //获取缓存到磁盘的路径
    -(NSString )makeDiskCachePath:(NSString)fullNamespace
    /
    存储图片到磁盘
    *recalculate表示是否需要把image类型转换为nsdata类型,如果不需要转换则把imageData直接写入磁盘,如果需要转换则把image转换为data
    */

<pre><code>

  • (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk
    {

    if (!image || !key) {
    return;
    }

    if (self.shouldCacheImagesInMemory) {
    NSUInteger cost = SDCacheCostForImage(image);
    [self.memCache setObject:image forKey:key cost:cost];//把图片存储到缓存
    }

    if (toDisk) {
    dispatch_async(self.ioQueue, ^{
    NSData *data = imageData;
    // image非空才能转换为data
    if (image && (recalculate || !data)) {

if TARGET_OS_IPHONE

            //获取图片的alpha信息,判断是否是png图片
            int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
            BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                              alphaInfo == kCGImageAlphaNoneSkipFirst ||
                              alphaInfo == kCGImageAlphaNoneSkipLast);
            BOOL imageIsPng = hasAlpha;
            
            if ([imageData length] >= [kPNGSignatureData length]) {
                imageIsPng = ImageDataHasPNGPreffix(imageData);
            }
            
            if (imageIsPng) {
                data = UIImagePNGRepresentation(image);
            }
            else {
                data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
            }

else

            data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];

endif

        }            
        // 把data写入磁盘
        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];
            // transform to NSUrl
            NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
            
            [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];
            
            // disable iCloud backup
            if (self.shouldDisableiCloud) {
                [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
            }
        }
    });
}

}
</code></pre>

//查询图片是否已存在

  • (BOOL)diskImageExistsWithKey:(NSString *)key;
  • (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;

//从缓存中取图片

  • (UIImage *)imageFromMemoryCacheForKey:(NSString *)key
    //从磁盘中取图片,先从内存缓存中取,无则从磁盘中取
  • (UIImage *)imageFromDiskCacheForKey:(NSString *)key
    //根据key生成文件路径,先从默认图片路径中查找,无则从自定义路径中查找,返回image的data数据
  • (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key;
    //根据key找到image
  • (UIImage *)diskImageForKey:(NSString *)key;
    //根据key中后缀@2x、@3x,放大图片到2、3倍
  • (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image;

<pre><code>//在磁盘中查找图片

  • (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) { doneBlock(image, SDImageCacheTypeMemory); return nil;
    }
    <code><pre>

     //缓存中没有,则从硬盘查找,放在ioQueue中处理
    

    NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @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); }); } }); return operation;
    }

//从缓存中移除图片,

  • (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion { if (key == nil) { return; } if (self.shouldCacheImagesInMemory) { [self.memCache removeObjectForKey:key]; } 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();
    }
    }
    //从磁盘中移除缓存图片文件夹

  • (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
    //每次退到后台,清除已过期文件,并判断缓存文件夹是否大于设置的最大容量

  • (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
    //初始化缓存路径URL
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
    //获取路径下文件的属性值,目录、文件修改日期、文件大小
    NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

      // 枚举器预先获取缓存文件的有用的属性
      NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL                                                   includingPropertiesForKeys:resourceKeys                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles                                                                 errorHandler:NULL];        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
      NSUInteger currentCacheSize = 0;
    
      NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];           
    

    for (NSURL *fileURL in fileEnumerator) {
    NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];

          // 是文件夹目录
          if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {                continue;            }
    
          // 文件最后一次修改的日期与过期日期比较谁更晚,laterDate函数返回两个日期中较晚的一个时间,如果修改日期小于过期日期则删除该文件
          NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {                [urlsToDelete addObject:fileURL];                continue;            }
    
          // 统计未过期文件的大小
          NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];            [cacheFiles setObject:resourceValues forKey:fileURL];
      }
      // 删除已过期文件
      for (NSURL *fileURL in urlsToDelete) {                 
      [_fileManager removeItemAtURL:fileURL error:nil];          
      }
    
      // 如果缓存文件容量已经大于设置的容量则删除最早的文件
      if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
          // 最大缓存容量的一半
          const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
    
          // 由修改日期对文件排序
          NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent      
                                      usingComparator:^NSComparisonResult(id obj1, id obj2) {                                                                
           return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];                                                            }];
    
          //删除文件直到文件夹的容量 < 最大容量的一半
          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(); }); } });
}

// 获取已缓存文件的总容量

  • (NSUInteger)getSize { __blockNSUInteger size = 0; dispatch_sync(self.ioQueue, ^{ NSDirectoryEnumerator *fileEnumerator = [_fileManagerenumeratorAtPath:self.diskCachePath]; for (NSString *fileName in fileEnumerator) { NSString *filePath = [self.diskCachePathstringByAppendingPathComponent:fileName]; NSDictionary *attrs = [[NSFileManagerdefaultManager] attributesOfItemAtPath:filePath error:nil]; size += [attrs fileSize]; } }); return size;}

// 获取已缓存图片的总数量

  • (NSUInteger)getDiskCount { __blockNSUInteger count = 0; dispatch_sync(self.ioQueue, ^{ NSDirectoryEnumerator *fileEnumerator = [_fileManagerenumeratorAtPath:self.diskCachePath]; count = [[fileEnumerator allObjects] count]; }); return count;
    }

// 计算文件大小及文件数量

  • (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock { NSURL *diskCacheURL = [NSURLfileURLWithPath:self.diskCachePathisDirectory:YES]; dispatch_async(self.ioQueue, ^{ NSUInteger fileCount = 0; NSUInteger totalSize = 0; NSDirectoryEnumerator *fileEnumerator = [_fileManagerenumeratorAtURL:diskCacheURL includingPropertiesForKeys:@[NSFileSize] options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; for (NSURL *fileURL in fileEnumerator) { NSNumber *fileSize; [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKeyerror:NULL]; totalSize += [fileSize unsignedIntegerValue]; fileCount += 1; } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(fileCount, totalSize); }); } });
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容