SDWebImage源码阅读
2017-08-03 15:00
381 查看
前言
SDWebImage强大的网络图片加载库,以前只是能够灵活使用,对底层的实现原理,也是知其然而不知其所以然,但是成为一名优秀的开发者来说,会用只是最简单的一步,更重要的是要研究其底层的技术实现和设计思路原理。这个月工作也不忙,所以阅读下类库源码。记录下自己的思考与总结。SDWebImage 简介
提供UIImageView的一个分类,用来加载网络图片并且对网络图片的缓存进行管理采用异步方式来下载网络图片
采用异步方式,使用memory+disk来缓存网络图片,自动管理缓存
支持GIF动画
支持WebP格式
后台图片解压缩处理
确保同一个URL的图片不被重复下载
失效的URL不会被无限重试
下载及缓存时,主线程不被阻塞
下载
在SDWebImage中,图片的下载是由SDWebImageDownloader类来完成的,它是一个异步的下载器。并且对图片的加载做了优化处理。下载选项
在下载的过程中,程序会根据设置的不同的下载选项,而执行不同操作。下载选项由枚举SDWebImageDownloaderOptions定义,定义如下:
123456789101112131415161718192021222324 | typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { // 默认的使用模式,前往下载,返回进度block信息,完成时调用completedBlock SDWebImageDownloaderLowPriority = 1 << 0, // 渐进式下载,如果设置了这个选项,会在下载过程中,每次接收到一段返回数据就会调用一次完成回调,回调中的image参数为未下载完成的部分图像,可以实现将图片一点点显示出来的功能 SDWebImageDownloaderProgressiveDownload = 1 << 1, // 默认情况下请求不使用NSURLCache,如果设置该选项,则以默认的缓存策略来使用NSURLCache SDWebImageDownloaderUseNSURLCache = 1 << 2, // 如果从NSURLcache缓存中读取图片,则在调用完成block的时候,传递空的image或者imageData SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, // 在iOS 4+系统上,允许程序进入后台后继续下载图片。该操作通过向系统申请额外时间来完成后台下载。如果后台任务终止,则操作将被取消 SDWebImageDownloaderContinueInBackground = 1 << 4, // 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES来处理存储在NSHTTPCookieStore中的cookie SDWebImageDownloaderHandleCookies = 1 << 5, // 允许不受信任的SSL证书。主要用于测试目的(生产环境慎用) SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, // 将图片下载放到高优先级队列中 SDWebImageDownloaderHighPriority = 1 << 7,}; |
下载选项
SDWebImage定义了两种下载顺序,定义如下:
12345678 | typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { // 默认下载顺序 以队列的方式,按照先进先出的顺序下载 SDWebImageDownloaderFIFOExecutionOrder, // 以栈的方式,按照后进先出的顺序下载 SDWebImageDownloaderLIFOExecutionOrder}; |
SDWebImageDownloader下载管理器是一个单例类,主要管理图片的下载操作。图片的下载是放在NSOperationQueue操作队列中来完成的,队列的默认最大并发数为6.设置超时时间为15s.
所有下载操作的网络响应序列化处理是放在一个自定义的并行调度队列中来处理的,其声明及定义如下:
123 | @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); |
123456 | // 下载进度(返回已经接收的图片数据的大小,未接收的图片数据的大小)typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);// 下载完成typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);// Header 过滤typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers); |
" target=_blank>; NSMutableDictionary *callbacks = [NSMutableDictionary new]; if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; [callbacksForURL addObject:callbacks]; self.URLCallbacks[url] = callbacksForURL;// 如果url第一次绑定它的回调,也就是第一次使用这个url创建下载任务则执行一次创建回调 if (first) { createCallback(); } });}
在barrierQueue队列中创建下载任务。至此下载的任务都创建好了,下面到下载的操作了
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 | - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock { __block SDWebImageDownloaderOperation *operation; [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{ NSTimeInterval timeoutInterval = wself.downloadTimeout; if (timeoutInterval == 0.0) { timeoutInterval = 15.0; } // 1. 创建请求对象,并根据options参数设置其属性 // 为了避免潜在的重复缓存(NSURLCache + SDImageCache),如果没有明确告知需要缓存,则禁用图片请求的缓存操作 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];// 通过设置 NSMutableURLRequest.HTTPShouldHandleCookies = YES的方式来处理存储在NSHTTPCookieStore的cookies request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);//返回在接到上一个请求得得响应之前,需要传输数据,YES传输,NO不传输 request.HTTPShouldUsePipelining = YES; }];};/**如果自定义了wself.headersFilter,那就用自己设置的wself.headersFilter来设置HTTP的header field它的定义是typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);一个返回结果为NSDictionary类型的block如果没有自己设置wself.headersFilter那么就用SDWebImage提供的HTTPHeadersHTTPHeaders在#import "SDWebImageDownloader.h",init方法里面初始化,下载webp图片需要的header不一样#ifdef SD_WEBP _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];#else _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];#endif*/ if (wself.headersFilter) { request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]); } else { request.allHTTPHeaderFields = wself.HTTPHeaders; }/**创建SDWebImageDownLoaderOperation操作对象(下载的操作就是在SDWebImageDownLoaderOperation类里面进行的)传入了进度回调,完成回调,取消回调*/ operation = [[wself.operationClass alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) { SDWebImageDownloader *sself = wself; if (!sself) return; __block NSArray *callbacksForURL;dispatch_sync(sself.barrierQueue, ^{ // 从管理器的callbacksForURL中找出该URL所有的进度处理回调并调用 callbacksForURL = [sself.URLCallbacks[url] copy]; }); for (NSDictionary *callbacks in callbacksForURL) { dispatch_async(dispatch_get_main_queue(), ^{ SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; if (callback) callback(receivedSize, expectedSize); }); } } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { // 从管理器的callbacksForURL中找出该URL所有的完成处理回调并调用, SDWebImageDownloader *sself = wself; if (!sself) return; __block NSArray *callbacksForURL; dispatch_barrier_sync(sself.barrierQueue, ^{ callbacksForURL = [sself.URLCallbacks[url] copy]; if (finished) { // 如果这个任务已经完成,就根据url这个key从URLCallbacks字典里面删除 [sself.URLCallbacks removeObjectForKey:url]; } }); for (NSDictionary *callbacks in callbacksForURL) { SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey]; if (callback) callback(image, data, error, finished); } } cancelled:^{ // 取消操作将该url对应的回调信息从URLCallbacks中删除 SDWebImageDownloader *sself = wself; if (!sself) return; dispatch_barrier_async(sself.barrierQueue, ^{ [sself.URLCallbacks removeObjectForKey:url]; }); }]; // 设置是否需要解压 operation.shouldDecompressImages = wself.shouldDecompressImages; if (wself.urlCredential) { operation.credential = wself.urlCredential; } else if (wself.username && wself.password) { operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; } if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; }// 将操作加入到操作队列downloadQueue中.如果是LIFO顺序,则将新的操作作为原队列中最后一个操作的依赖,然后将新操作设置为最后一个操作 [wself.downloadQueue addOperation:operation]; if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } return operation;} |
每个图片的下载都是一个Operation操作。SDWebImage自定义了一个SDWebImageDownloaderOperation类,继承自NSOperation。并遵守SDWebImageOperation协议。对于图片的下载。使用的是NSURLConnection(未使用7.0以后的NSURLSession)。在SDWebImageDownloaderOperation中重写了start方法,方便自己管理下载的状态。此方法是执行下载任务的核心代码。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 | - (void)start { @synchronized (self) { // 管理下载状态,如果已取消,则重置当前下载并设置完成状态为YES if (self.isCancelled) { self.finished = YES; [self reset]; return; }#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 // 如果设置了在后台执行,则进行后台执行 if ([self shouldContinueWhenAppEntersBackground]) { __weak __typeof__ (self) wself = self; self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ ... } }]; }#endif self.executing = YES; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; self.thread = [NSThread currentThread]; } [self.connection start]; if (self.connection) { if (self.progressBlock) { self.progressBlock(0, NSURLResponseUnknownLength); } // 在主线程中发送开始下载的通知 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; });/**在[self.connection start];有返回结果之前,代码会一直阻塞在CFRunLoopRun这里,也就是说下载就一直在进行中,一直到下载完成或者错误(这两种情况都会调用CFRunLoopStop),阻塞才会解除*/ // 开启当前线程的runloop if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false); } else { CFRunLoopRun(); } // 如果未完成,则取消连接 if (!self.isFinished) { [self.connection cancel]; [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]]; } } else { //如果connection创建失败,直接执行完成回调,并传递一个connection没有初始化的错误 if (self.completedBlock) { self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES); } }// 下载操作已经完成了,停止在后台的执行,使用endBackgroundTask#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 if (self.backgroundTaskId != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId]; self.backgroundTaskId = UIBackgroundTaskInvalid; }#endif} |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 拼接数据 [self.imageData appendData:data]; if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) { // 获取已接收的数据的长度 const NSInteger totalSize = self.imageData.length; // 每次接收到数据时,都会用现有的数据创建一个CGImageSourceRef对象以做处理,而且这个数据应该是已接收的全部数据,而不仅仅是新的字节 CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL); /**在首次接收到数据的时候,图片的长宽都是0(width+height == 0)先从这些包含图像信息的数据中取出图像的长,宽,方向等信息以备使用*/ if (width + height == 0) { CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); if (properties) { NSInteger orientationValue = -1; CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight); if (val) CFNumberGetValue(val, kCFNumberLongType, &height); ... CFRelease(properties); /** 使用Core Craphics框架绘制image时,使用的是 initWithCGImage这个函数,但是使用这个函数有时候会造成图片朝向的错误,所以在这里保存朝向信息,orientation是一个可以记录图片方向的枚举 */ orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)]; } } // 图片还未接收完全 if (width + height > 0 && totalSize < self.expectedSize) { // 使用现有的数据创建图片对象,如果数据中存有多张图片,则取第一张 CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);#ifdef TARGET_OS_IPHONE // 对下载下来的图片做个颜色空间转换等处理 if (partialImageRef) { const size_t partialHeight = CGImageGetHeight(partialImageRef); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); CGColorSpaceRelease(colorSpace); if (bmContext) { CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef); CGImageRelease(partialImageRef); partialImageRef = CGBitmapContextCreateImage(bmContext); CGContextRelease(bmContext); } else { CGImageRelease(partialImageRef); partialImageRef = nil; } }#endif // 对图片进行缩放、解码操作 if (partialImageRef) { UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation]; NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; UIImage *scaledImage = [self scaledImageForKey:key image:image]; image = [UIImage decodedImageWithImage:scaledImage]; CGImageRelease(partialImageRef); dispatch_main_sync_safe(^{ if (self.completedBlock) { self.completedBlock(image, nil, nil, NO); } }); } } CFRelease(imageSource); } if (self.progressBlock) { self.progressBlock(self.imageData.length, self.expectedSize); }} |
缓存
SDWebImage提供了对图片缓存的支持,而该功能是由SDImageCache类来完成的。该类负责处理内存缓存及磁盘缓存。其中磁盘缓存的写操作是异步的。内存缓存
NSCache是一个类似于集合的容器,即缓存。它存储key-value,这一点非常类似 NSDictionary。 一般用 NSCache来缓存临时存储短时间但是使用创建成本高的对象,重用这些对象可以优化性能,因为他们的值不需要被重新计算。另外一方面,这些对象对于程序来说是不要紧的,在内存紧张的时候会被丢弃,如果对象被丢弃了,则下次使用的时候需要重新计算
磁盘缓存
磁盘缓存则是使用NSFileManager对象来实现的。图片存储的位置是位于Cache文件夹。此外SDImageCache还定义了一个串行队列,来异步存储图片
图片存储
在iOS中,会先检测图片是PNG还是JPEG,并将其转换为相应的图片数据,最后将数据写入到磁盘中。判断是否是png格式的文件,除了看是不是.png后缀格式命名外,还能分析文件开头的部分数据,这部分数据就是文件签名,每个标准的PNG文件开头都有固定格式的数字签名。详细参考文件名的命名规则则是按照缓存的key做md5处理。
12345678910111213 | - (NSString *)cachedFileNameForKey:(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;} |
1234567891011121314151617181920212223242526272829303132333435363738394041 | - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk { if (!image || !key) { return; } if (self.shouldCacheImagesInMemory) { // //查询图片大小,并存入内存(NSCache)中 NSUInteger cost = SDCacheCostForImage(image); [self.memCache setObject:image forKey:key cost:cost]; }// 加载到磁盘 if (toDisk) { dispatch_async(self.ioQueue, ^{ NSData *data = imageData;// 是否要重新处理图片数据 if (image && (recalculate || !data)) {#if TARGET_OS_IPHONE// 确定图片是png还是jpeg. imageData为nil的话当做是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 }// 创建缓存文件并存储图片 [self storeImageDataToDisk:data forKey:key]; }); }} |
使用key作为参数,查询内存和磁盘中是否有对应的图片
12 | - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;- (UIImage *)imageFromDiskCacheForKey:(NSString *)key; |
使用如下操作可以移除内存或者磁盘上的图片。
1234 | - (void)removeImageForKey:(NSString *)key;- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion; |
两种清理方式:完全清空和部分清空
完全清空是直接把文件夹移除掉
12 | - (void)clearMemory- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion |
文件的缓存有效期:默认是一周。如果文件的缓存时间超过这个时间值,则将其移除
最大缓存空间大小:如果所有缓存文件的总大小超过最大缓存空间,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 | - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock { dispatch_async(self.ioQueue, ^{ 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; } // 移除过期文件 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(); }); } });} |
缓存分为内存缓存和磁盘缓存(以NSCache和文件的形式)
读取先从内存查找、如果没有再从磁盘读取并放入内存
提供完全移除和部分移除功能,部分移除根据配置、达到删除文件后的容量小于用户设定的最大值
SDWebImageManager
1234 | @interface SDWebImageManager : NSObject@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;@property (strong, nonatomic, readonly) SDImageCache *imageCache;@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader; |
12345 | // 缓存图片没有找到的时候,去哪里下载- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;// 允许在图片已经被下载完成且被缓存到磁盘或内存前立即转换- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL; |
12345678910111213 | #define dispatch_main_sync_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_sync(dispatch_get_main_queue(), block);\ }#define dispatch_main_async_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ } |
补充点
之前一直不明白,为什么将图片从磁盘读取出来后需要做Decode,后来参考才明白。由于UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,所以在每次画图的时候,会有一个解压操作,这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了。这是典型的空间换时间的做法。
相关文章推荐
- 【原】SDWebImage源码阅读(一)
- SDWebImage 源码阅读笔记(四)
- 【原】SDWebImage源码阅读(二)
- [iOS]SDWebImage 源码阅读(二)缓存
- IOS SDWebImage 2.X源码阅读(三)
- IOS SDWebImage 2.X源码阅读(四)
- SDWebImage 源码阅读(三)
- 【原】SDWebImage源码阅读(五)
- [iOS]SDWebImage 源码阅读(一)
- SDWebImage源码阅读-第一篇
- 源码阅读:SDWebImage
- SDWebImage源码阅读-第三篇
- SDWebImage 源码阅读(四)
- [iOS]SDWebImage 源码阅读(三)下载
- SDWebImage 源码阅读分享
- 【原】SDWebImage源码阅读(三)
- SDWebImage 源码阅读笔记(一)
- IOS SDWebImage 2.X源码阅读(一)
- SDWebImage 源码阅读笔记(二)
- 【原】SDWebImage源码阅读(四)