存 取 删 路径html
是在storeImage这个方法里:缓存
将图片储存到内存和硬盘上网络
-(void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk { if (!image || !key) { return; } // if memory cache is enabled 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存在,可是须要从新计算(recalculate)或者data为空 // 那就要根据image从新生成新的data // 不过要是连image也为空的话,那就别存了 if (image && (recalculate || !data)) { #if TARGET_OS_IPHONE // 咱们须要判断image是PNG仍是JPEG // PNG的图片很容易检测出来,由于它们有一个特定的标示 (http://www.w3.org/TR/PNG-Structure.html) // PNG图片的前8个字节不准符合下面这些值(十进制表示) // 137 80 78 71 13 10 26 10 // 若是imageData为空l (举个例子,好比image在下载后须要transform,那么就imageData就会为空) // 而且image有一个alpha通道, 咱们将该image看作PNG以免透明度(alpha)的丢失(由于JPEG没有透明色) int alphaInfo = CGImageGetAlphaInfo(image.CGImage);// 获取image中的透明信息 // 该image中确实有透明信息,就认为image为PNG BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast); BOOL imageIsPng = hasAlpha; // 可是若是咱们已经有了imageData,咱们就能够直接根据data中前几个字节判断是否是PNG if ([imageData length] >= [kPNGSignatureData length]) { // ImageDataHasPNGPreffix就是为了判断imageData前8个字节是否是符合PNG标志 imageIsPng = ImageDataHasPNGPreffix(imageData); } // 若是image是PNG格式,就是用UIImagePNGRepresentation将其转化为NSData,不然按照JPEG格式转化,而且压缩质量为1,即无压缩 if (imageIsPng) { data = UIImagePNGRepresentation(image); } else { data = UIImageJPEGRepresentation(image, (CGFloat)1.0); } #else // 固然,若是不是在iPhone平台上,就使用下面这个方法。不过不在咱们研究范围以内 data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil]; #endif } // 获取到须要存储的data后,下面就要用fileManager进行存储了 if (data) { // 首先判断disk cache的文件路径是否存在,不存在的话就建立一个 // disk cache的文件路径是存储在_diskCachePath中的 if (![_fileManager fileExistsAtPath:_diskCachePath]) { [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } // 根据image的key(通常状况下理解为image的url)组合成最终的文件路径 // 上面那个生成的文件路径只是一个文件目录,就跟/cache/images/img1.png和cache/images/的区别同样 NSString *cachePathForKey = [self defaultCachePathForKey:key]; // 这个url可不是网络端的url,而是file在系统路径下的url // 好比/foo/bar/baz --------> file:///foo/bar/baz NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; // 根据存储的路径(cachePathForKey)和存储的数据(data)将其存放到iOS的文件系统 [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil]; // disable iCloud backup if (self.shouldDisableiCloud) { [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil]; } } }); } }
内存缓存使用NSCache的objectForKey取数据:异步
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key { return [self.memCache objectForKey:key]; }
磁盘取数据 不断用 dataWithContentsOfFile来试数据是否在key对应的路径中async
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key { // First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { return image; } // Second check the disk cache... UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } return diskImage; }
看其中最长的一个:函数
// 实现了一个简单的缓存清除策略:清除修改时间最先的file - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock { dispatch_async(self.ioQueue, ^{ // 这两个变量主要是为了下面生成NSDirectoryEnumerator准备的 // 一个是记录遍历的文件目录,一个是记录遍历须要预先获取文件的哪些属性 NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; // 递归地遍历diskCachePath这个文件夹中的全部目录,此处不是直接使用diskCachePath,而是使用其生成的NSURL // 此处使用includingPropertiesForKeys:resourceKeys,这样每一个file的resourceKeys对应的属性也会在遍历时预先获取到 // NSDirectoryEnumerationSkipsHiddenFiles表示不遍历隐藏文件 NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; // 获取文件的过时时间,SDWebImage中默认是一个星期 // 不过这里虽然称*expirationDate为过时时间,可是实质上并非这样。 // 实际上是这样的,好比在2015/12/12/00:00:00最后一次修改文件,对应的过时时间应该是 // 2015/12/19/00:00:00,不过如今时间是2015/12/27/00:00:00,我先将当前时间减去1个星期,获得 // 2015/12/20/00:00:00,这个时间才是咱们函数中的expirationDate。 // 用这个expirationDate和最后一次修改时间modificationDate比较看谁更晚就行。 NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge]; // 用来存储对应文件的一些属性,好比文件所需磁盘空间 NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; // 记录当前已经使用的磁盘缓存大小 NSUInteger currentCacheSize = 0; // 在缓存的目录开始遍历文件. 这次遍历有两个目的: // // 1. 移除过时的文件 // 2. 同时存储每一个文件的属性(好比该file是不是文件夹、该file所需磁盘大小,修改时间) NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; for (NSURL *fileURL in fileEnumerator) { NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; // 当前扫描的是目录,就跳过 if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { continue; } // 移除过时文件 // 这里判断过时的方式:对比文件的最后一次修改日期和expirationDate谁更晚,若是expirationDate更晚,就认为该文件已通过期,具体解释见上面 NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // 计算当前已经使用的cache大小, // 并将对应file的属性存到cacheFiles中 NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } for (NSURL *fileURL in urlsToDelete) { // 根据须要移除文件的url来移除对应file [_fileManager removeItemAtURL:fileURL error:nil]; } // 若是咱们当前cache的大小已经超过了容许配置的缓存大小,那就删除已经缓存的文件。 // 删除策略就是,首先删除修改时间更早的缓存文件 if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { // 直接将当前cache大小降到容许最大的cache大小的通常 const NSUInteger desiredCacheSize = self.maxCacheSize / 2; // 根据文件修改时间来给全部缓存文件排序,按照修改时间越早越在前的规则排序 NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; // 每次删除file后,就计算此时的cache的大小 // 若是此时的cache大小已经降到指望的大小了,就中止删除文件了 for (NSURL *fileURL in sortedFiles) { if ([_fileManager removeItemAtURL:fileURL error:nil]) { // 获取该文件对应的属性 NSDictionary *resourceValues = cacheFiles[fileURL]; // 根据resourceValues获取该文件所需磁盘空间大小 NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; // 计算当前cache大小 currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) { break; } } } } // 若是有completionBlock,就在主线程中调用 if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } }); }
// 简单封装了cachePathForKey:inPath - (NSString *)defaultCachePathForKey:(NSString *)key { return [self cachePathForKey:key inPath:self.diskCachePath]; } // cachePathForKey:inPath - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path { // 根据传入的key建立最终要存储时的文件名 NSString *filename = [self cachedFileNameForKey:key]; // 将存储的文件路径和文件名绑定在一块儿,做为最终的存储路径 return [path stringByAppendingPathComponent:filename]; } // cachedFileNameForKey: - (NSString *)cachedFileNameForKey:(NSString *)key { const char *str = [key UTF8String]; if (str == NULL) { str = ""; } // 使用了MD5进行加密处理 // 开辟一个16字节(128位:md5加密出来就是128bit)的空间 unsigned char r[CC_MD5_DIGEST_LENGTH]; // 官方封装好的加密方法 // 把str字符串转换成了32位的16进制数列(这个过程不可逆转) 存储到了r这个空间中 CC_MD5(str, (CC_LONG)strlen(str), r); // 最终生成的文件名就是 "md5码"+".文件类型" 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; }