美文网首页
SDWebImage源码解析

SDWebImage源码解析

作者: 百省 | 来源:发表于2016-07-12 16:08 被阅读98次

    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); }); } });
      }

    相关文章

      网友评论

          本文标题:SDWebImage源码解析

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