前言 SDWebImage 是一个图片加载库,目前在Github上已有将近14k的star了,这篇文章记录了阅读SDWebImage源码的笔记(Version 3.8),简单的研究了它的实现细节。
流程 SDWebImage加载图片的大致流程,以UIImageView+WebCache为例。
1.调用基础入口方法sd_setImageWithURL:
,sd_setImageWithURL:placeholderImage:
,sd_setImageWithURL:placeholderImage:options:
等等
2.进入sd_setImageWithURL:placeholderImage:options:progress:completed:
3.进入downloadImageWithURL:options:progress:completed:
4.调用SDWebImageDownloader下载图片downloadImageWithURL:options:progress:completed:
5.调用addProgressCallback:completedBlock:forURL:createCallback:
6.初始化SDWebImageDownloaderOperationinitWithRequest:inSession:options:progress:completed:cancelled:
7.缓存图片调用storeImage:recalculateFromImage:imageData:forKey:toDisk:
以上方法是调用链上的主要方法,将在下文逐一解释。
Utils Utils有三个类:
SDWebImageManager
SDWebImage核心类,用来管理图片的下载和缓存
SDWebImageDecoder
解压缩图片
SDWebImagePrefetcher
可以预先下载图片
SDWebImageManager 用来管理图片的下载和缓存工具类,提取了SDImageCache
和SDWebImageDownloader
的相关方法,在流程3中使用的方法具体为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock { NSAssert (completedBlock != nil , @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead" ); if ([url isKindOfClass:NSString .class ]) { url = [NSURL URLWithString:(NSString *)url]; } if (![url isKindOfClass:NSURL .class ]) { url = nil ; } __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; __weak SDWebImageCombinedOperation *weakOperation = operation; BOOL isFailedUrl = NO ; @synchronized (self .failedURLs ) { isFailedUrl = [self .failedURLs containsObject:url]; } if (url.absoluteString .length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { dispatch_main_sync_safe(^{ NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil ]; completedBlock(nil , error, SDImageCacheTypeNone, YES , url); }); return operation; } @synchronized (self .runningOperations ) { [self .runningOperations addObject:operation]; } NSString *key = [self cacheKeyForURL:url]; operation.cacheOperation = [self .imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { if (operation.isCancelled ) { @synchronized (self .runningOperations ) { [self .runningOperations removeObject:operation]; } return ; } if ((!image || options & SDWebImageRefreshCached) && (![self .delegate respondsToSelector:@selector (imageManager:shouldDownloadImageForURL:)] || [self .delegate imageManager:self shouldDownloadImageForURL:url])) { if (image && options & SDWebImageRefreshCached) { dispatch_main_sync_safe(^{ completedBlock(image, nil , cacheType, YES , url); }); } SDWebImageDownloaderOptions downloaderOptions = 0 ; if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache ; if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; if (image && options & SDWebImageRefreshCached) { downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } id <SDWebImageOperation> subOperation = [self .imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { __strong __typeof (weakOperation) strongOperation = weakOperation; if (!strongOperation || strongOperation.isCancelled ) { } else if (error) { dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled ) { completedBlock(nil , error, SDImageCacheTypeNone, finished, url); } }); if ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost ) { @synchronized (self .failedURLs ) { [self .failedURLs addObject:url]; } } } else { if ((options & SDWebImageRetryFailed)) { @synchronized (self .failedURLs ) { [self .failedURLs removeObject:url]; } } BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); if (options & SDWebImageRefreshCached && image && !downloadedImage) { } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self .delegate respondsToSelector:@selector (imageManager:transformDownloadedImage:withURL:)]) { dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{ UIImage *transformedImage = [self .delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; [self .imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk]; } dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled ) { completedBlock(transformedImage, nil , SDImageCacheTypeNone, finished, url); } }); }); } else { if (downloadedImage && finished) { [self .imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled ) { completedBlock(downloadedImage, nil , SDImageCacheTypeNone, finished, url); } }); } } if (finished) { @synchronized (self .runningOperations ) { if (strongOperation) { [self .runningOperations removeObject:strongOperation]; } } } }]; operation.cancelBlock = ^{ [subOperation cancel]; @synchronized (self .runningOperations ) { __strong __typeof (weakOperation) strongOperation = weakOperation; if (strongOperation) { [self .runningOperations removeObject:strongOperation]; } } }; } else if (image) { dispatch_main_sync_safe(^{ __strong __typeof (weakOperation) strongOperation = weakOperation; if (strongOperation && !strongOperation.isCancelled ) { completedBlock(image, nil , cacheType, YES , url); } }); @synchronized (self .runningOperations ) { [self .runningOperations removeObject:operation]; } } else { dispatch_main_sync_safe(^{ __strong __typeof (weakOperation) strongOperation = weakOperation; if (strongOperation && !weakOperation.isCancelled ) { completedBlock(nil , nil , SDImageCacheTypeNone, YES , url); } }); @synchronized (self .runningOperations ) { [self .runningOperations removeObject:operation]; } } }]; return operation; }
此为核心方法,完成了查找缓存、下载图片、缓存图片等操作。值得注意的是SDWebImageManager还有一个delegate属性,并提供了两个可选方法:
1 2 3 4 5 6 7 8 9 @protocol SDWebImageManagerDelegate <NSObject >@optional - (BOOL )imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL; - (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL; @end
SDWebImageDecoder 用来解压缩图片,会在取图片时做一次decode操作。 因为通过 imageNamed 创建 UIImage 时,系统实际上只是在 Bundle 内查找到文件名,然后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存去。在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。具体的说就是一个UIImage加载了jpeg或者png,当UIImageView将要显示这个UIImage的时候会先把png和jpeg解码成未压缩格式,所以SDWebImage有一个decodeImage方法,就是把这一步放在了异步线程做,防止tableViewCell中的imageView加载图片的时候在主线程解码图片,导致滑动卡顿。这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了,这种做法是典型的空间换时间的做法,如下从硬盘中去图片时,分别对图片进行了缩放和解压缩操作。 关于处理图片可以看看YY这篇博文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (UIImage *)diskImageForKey:(NSString *)key { NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; if (data) { UIImage *image = [UIImage sd_imageWithData:data]; image = [self scaledImageForKey:key image:image]; if (self .shouldDecompressImages ) { image = [UIImage decodedImageWithImage:image]; } return image; } else { return nil ; } }
DownLoader DownLoader有两个类:
SDWebImageDownloader
下载核心类,用来管理图片的下载
SDWebImageDownloaderOperation
每一个图片下载的具体操作
SDWebImageDownloader SDWebImageDownloader
一个单例类,负责图片的下载操作和管理,其在流程4和流程5中的主要方法为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock { __block SDWebImageDownloaderOperation *operation; __weak __typeof (self )wself = self ; [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{ NSTimeInterval timeoutInterval = wself.downloadTimeout ; if (timeoutInterval == 0.0 ) { timeoutInterval = 15.0 ; } NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData ) timeoutInterval:timeoutInterval]; request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); request.HTTPShouldUsePipelining = YES ; if (wself.headersFilter ) { request.allHTTPHeaderFields = wself.headersFilter (url, [wself.HTTPHeaders copy ]); } else { request.allHTTPHeaderFields = wself.HTTPHeaders ; } operation = [[wself.operationClass alloc] initWithRequest:request inSession:self .session options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) { SDWebImageDownloader *sself = wself; if (!sself) return ; __block NSArray *callbacksForURL; dispatch_sync (sself.barrierQueue , ^{ 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) { SDWebImageDownloader *sself = wself; if (!sself) return ; __block NSArray *callbacksForURL; dispatch_barrier_sync(sself.barrierQueue , ^{ callbacksForURL = [sself.URLCallbacks [url] copy ]; if (finished) { [sself.URLCallbacks removeObjectForKey:url]; } }); for (NSDictionary *callbacks in callbacksForURL) { SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey]; if (callback) callback(image, data, error, finished); } } cancelled:^{ 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 ; } [wself.downloadQueue addOperation:operation]; if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } }]; return operation; } - (void )addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback { if (url == nil ) { if (completedBlock != nil ) { completedBlock(nil , nil , nil , NO ); } return ; } dispatch_barrier_sync(self .barrierQueue , ^{ BOOL first = NO ; if (!self .URLCallbacks [url]) { self .URLCallbacks [url] = [NSMutableArray new]; first = YES ; } NSMutableArray *callbacksForURL = self .URLCallbacks [url]; NSMutableDictionary *callbacks = [NSMutableDictionary new]; if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy ]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy ]; [callbacksForURL addObject:callbacks]; self .URLCallbacks [url] = callbacksForURL; if (first) { createCallback(); } }); }
需要注意的是SDWebImageDownloader
管理的下载是放在一个NSOperationQueue
队列中来完成的,其中每个下载操作都是一个单独的SDWebImageDownloaderOperation
,将这些操作放到一个操作队列中,这样可以实现图片的并发下载。
SDWebImageDownloaderOperation SDWebImageDownloaderOperation
继承自NSOperation,实际中每个图片的下载都交给一个SDWebImageDownloaderOperation
,需要注意的是在SDWebImage Version3.8中,下载已经有原先的NSURLConnection切换到了NSURLSession了,所以我们要关注的是NSURLSessionDataDelegate中的URLSession:dataTask:didReceiveResponse:completionHandler:
方法,此方法主要用来接收数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 - (void )URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self .imageData appendData:data]; if ((self .options & SDWebImageDownloaderProgressiveDownload) && self .expectedSize > 0 && self .completedBlock ) { const NSInteger totalSize = self .imageData .length ; CGImageSourceRef imageSource = CGImageSourceCreateWithData ((__bridge CFDataRef )self .imageData , NULL ); 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); val = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth ); if (val) CFNumberGetValue (val, kCFNumberLongType , &width); val = CFDictionaryGetValue (properties, kCGImagePropertyOrientation ); if (val) CFNumberGetValue (val, kCFNumberNSIntegerType , &orientationValue); CFRelease (properties); 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.0 f, .origin .y = 0.0 f, .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]; if (self .shouldDecompressImages ) { image = [UIImage decodedImageWithImage:scaledImage]; } else { image = 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 ); } }
当然SDWebImageDownloaderOperation
本质是个NSOperation,有start方法启动,并在start方法里创建NSURLSession下载图片,更具体的可以看我注解的源码,这里不再赘述。
Cache 主要就一个类SDImageCache
,这个类管理了对图片的缓存操作,包括内存缓存和磁盘缓存。 内存缓存是用NSCache实现的,以Key-Value的形式存储图片,当内存不够的时候会清除所有缓存图片。 磁盘缓存则是缓存到沙盒中,需要注意的文件名是key做MD5后的字符串,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。 流程7中调用的主要代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 - (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; if (image && (recalculate || !data)) { #if TARGET_OS_IPHONE 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]; }); } }
磁盘缓存的自动清理机制为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 - (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(); }); } }); }
SDWebImageManager下载图片前会先搜索内存层面的数据,如果有直接返回,没有的话去访问磁盘,将图片从磁盘读取出来,然后做Decoder,再将图片对象放到内存做备份。 更为具体关于图片的查询、删除可以看我注解的源码,这里不再赘述。
Categories 这里主要看UIImageView+WebCache
这个类,在流程2中核心方法为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 - (void )sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock { [self sd_cancelCurrentImageLoad]; objc_setAssociatedObject(self , &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); if (!(options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ self .image = placeholder; }); } if (url) { if ([self showActivityIndicatorView]) { [self addActivityIndicator]; } __weak __typeof (self )wself = self ; id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { [wself removeActivityIndicator]; if (!wself) return ; dispatch_main_sync_safe(^{ if (!wself) return ; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return ; } else if (image) { wself.image = image; [wself setNeedsLayout]; } else { if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad" ]; } else { dispatch_main_async_safe(^{ [self removeActivityIndicator]; if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url" }]; completedBlock(nil , error, SDImageCacheTypeNone, url); } }); } }
总结
1.主动为开发者考虑1 2 3 4 5 if ([url isKindOfClass: NSString.class ]) { url = [NSURL URLWithString: (NSString *)url]; }
代码里在判断url的合法性上,主动帮将NSString当NSURL的新手擦屁股,连这个问题都考虑到了,是不是有很多在这里报错的人给他提issue呢?(笑~
1 2 3 4 5 6 if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ }
目的是想在主线程中执行操作,为什么要判断当前线程是否就是主线程中呢? 如果在主线程中执行dispatch_sync(dispatch_get_main_queue(), block) 同步操作时,会出现死锁问题,因为主线程正在执行当前代码,根本无法将block添加到主队列中。
4.@synchronized 相当于加锁,在SDWebImage里表现为@synchronized(syncObject) { }
,具体的说,是在某个对象实例内,可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 这里引申一个,若同时重写某属性的setter和getter方法后,需加上@synthesize propertyName = _propertyName;
,这是因为同时重写后,系统就不会帮你自动生成这个成员变量。
5.开启后台任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -(void )backgroundCleanDisk { Class UIApplicationClass = NSClassFromString (@"UIApplication" ); if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector (sharedApplication)]) { return ; } UIApplication *application = [UIApplication performSelector:@selector (sharedApplication)]; __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid ; }]; [self cleanDiskWithCompletionBlock:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid ; }]; }
想要提高还得看源码,总的来说阅读源码获益匪浅。 相关的注解在这里 。
Reference https://github.com/rs/SDWebImage http://blog.csdn.net/uxyheaven/article/details/7909373 http://southpeak.github.io/blog/2015/02/07/sourcecode-sdwebimage/