/** * 缓存图片 * * @param image 图片 * @param recalculate 是否重新计算 * @param imageData imageData * @param key 缓存的key * @param toDisk 是否缓存到磁盘 */ - (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 // We need to determine if the image is a PNG or a JPEG // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html) // The first eight bytes of a PNG file always contain the following (decimal) values: // 137 80 78 71 13 10 26 10
// If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download) // and the image has an alpha channel, we will consider it PNG to avoid losing the transparency // 判断是PNG还是JPEG int alphaInfo = CGImageGetAlphaInfo(image.CGImage); BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast); BOOL imageIsPng = hasAlpha;
// But if we have an image data, we will look at the preffix 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 } // 有数据 则缓存 if (data) { // 缓存路径是否存在 没有存在则创建 if (![_fileManager fileExistsAtPath:_diskCachePath]) { [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; }
// Enumerate all of the files in the cache directory. This loop has two purposes: // // 1. Removing files that are older than the expiration date. // 2. Storing file attributes for the size-based cleanup pass. // 需要删除的文件 如果是目录则跳过 将没有过期的文件下来并且计算它们的大小(如果该大小超过最大的阀值 则得删除一些缓存) NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; for (NSURL *fileURL in fileEnumerator) { NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
// Skip directories. if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { continue; }
// Remove files that are older than the expiration date; NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; }
// Store a reference to this file and account for its total size. NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } // 删掉过期的文件 for (NSURL *fileURL in urlsToDelete) { [_fileManager removeItemAtURL:fileURL error:nil]; }
// If our remaining disk cache exceeds a configured maximum size, perform a second // size-based cleanup pass. We delete the oldest files first. // 如果重新需要缓存的文件大小超过已经设定的缓存大小阀值 就得按时间来删除一些文件 知道缓存的文件大小小于已设定值的一半 if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { // Target half of our maximum cache size for this cleanup pass. const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first). NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }];
// Delete files until we fall below our desired cache size. 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(); }); } }); }
/** * 在后台清理 */ - (void)backgroundCleanDisk { Class UIApplicationClass = NSClassFromString(@"UIApplication"); if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { return; } UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ // Clean up any unfinished task business by marking where you // stopped or ending the task outright. [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }];
// Start the long-running task and return immediately. [self cleanDiskWithCompletionBlock:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; }
/** * By default, request prevent the of NSURLCache. With this flag, NSURLCache * is used with default policies. */ /// 默认情况下请求是不用NSURLCache的。但是如果使用该标志,默认的缓存策略就是使用NSURLCache SDWebImageDownloaderUseNSURLCache = 1 << 2,
/** * Call completion block with nil image/imageData if the image was read from NSURLCache * (to be combined with `SDWebImageDownloaderUseNSURLCache`). */ /// 如果图片是从NSURLCache里面读取,则使用nil来作为回调block的入参(与`SDWebImageDownloaderUseNSURLCache`组合使用) SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, /** * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * extra time in background to let the request finish. If the background task expires the operation will be cancelled. */ /// 在iOS4+ 能够在程序进入后台后继续下图片,通过向系统申请额外的时间在后台来完成数据请求操作。如果后台任务终止,则该操作会被取消 SDWebImageDownloaderContinueInBackground = 1 << 4,
/** * Handles cookies stored in NSHTTPCookieStore by setting * NSMutableURLRequest.HTTPShouldHandleCookies = YES; */ /// 通过设置 NSMutableURLRequest.HTTPShouldHandleCookies = YES 来处理存储在 NSHTTPCookieStore 的cookies SDWebImageDownloaderHandleCookies = 1 << 5,
/** * Enable to allow untrusted SSL certificates. * Useful for testing purposes. Use with caution in production. */ /// 允许使用不信任的SSL证书 这主要是用于测试的目的 在生产环境中得小心 SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/** * Put the image in the high priority queue. */ /// 将图片下载置于优先级较高的队列 SDWebImageDownloaderHighPriority = 1 << 7, };
下载顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/// 执行顺序 执行顺序有先进先出、先进后出两种方式。 typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { /** * Default value. All download operations will execute in queue style (first-in-first-out). */ // 先进先出 默认操作顺序 SDWebImageDownloaderFIFOExecutionOrder,
/** * All download operations will execute in stack style (last-in-first-out). */ // 先进后出 SDWebImageDownloaderLIFOExecutionOrder };
@interface SDWebImageDownloader () // 下载操作队列 @property (strong, nonatomic) NSOperationQueue *downloadQueue; // 最后添加的操作 先进后出顺序顺序 @property (weak, nonatomic) NSOperation *lastAddedOperation; // 图片下载类 @property (assign, nonatomic) Class operationClass; // URL回调字典 以URL为key,你装有URL下载的进度block和完成block的数组为value @property (strong, nonatomic) NSMutableDictionary *URLCallbacks; // HTTP请求头 @property (strong, nonatomic) NSMutableDictionary *HTTPHeaders; // This queue is used to serialize the handling of the network responses of all the download operation in a single queue // 并行的处理所有下载操作的网络响应 @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
// 创建请求 并根据下载选项设置请求的相关属性 // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise // 为了防止潜在的重复缓存(NSURLCache + SDImageCache),如果没有告诉我们需要缓存,我们不使用图片请求的缓存操作 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval]; // 是否处理cookies request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); request.HTTPShouldUsePipelining = YES; if (wself.headersFilter) { request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]); } else { request.allHTTPHeaderFields = wself.HTTPHeaders; } // 创建下载对象 在这里是 SDWebImageDownloaderOperation 类 operation = [[wself.operationClass alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) { // strong self SDWebImageDownloader *sself = wself; if (!sself) return; // URL回调数组 __block NSArray *callbacksForURL; dispatch_sync(sself.barrierQueue, ^{ // 根据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) { 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; // URL证书、用户名密码 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) { // Emulate LIFO execution order by systematically adding new operations as last operation's dependency [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } }];
return operation; }
/** * 添加设置回调 * * @param progressBlock 下载进度block * @param completedBlock 下载完成block * @param url 下载URL * @param createCallback 创建回调block */ - (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback { // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data. // URL不能为空 if (url == nil) { if (completedBlock != nil) { completedBlock(nil, nil, nil, NO); } return; } // dispatch_barrier_sync 保证同一时间只有一个线程操作 URLCallbacks dispatch_barrier_sync(self.barrierQueue, ^{ // 是否第一次操作 BOOL first = NO; if (!self.URLCallbacks[url]) { self.URLCallbacks[url] = [NSMutableArray new]; first = YES; }
// Handle single download of simultaneous download request for the same URL // 处理 同一个URL的单个下载 NSMutableArray *callbacksForURL = self.URLCallbacks[url]; NSMutableDictionary *callbacks = [NSMutableDictionary new]; // 将 进度block和完成block赋值 if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; [callbacksForURL addObject:callbacks]; // 已URL为key进行赋值 self.URLCallbacks[url] = callbacksForURL; // 如果是第一次下载 则回调 if (first) { createCallback(); } }); }
/** * 接受到服务端反应 * * @param connection connection description * @param response response description */ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { //'304 Not Modified' is an exceptional one // '304 Not Modified' 特殊处理 if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) { // 下载图片的期望大小 NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0; self.expectedSize = expected; // 进度block回调 if (self.progressBlock) { self.progressBlock(0, expected); } // 创建imageData self.imageData = [[NSMutableData alloc] initWithCapacity:expected]; // response赋值 self.response = response; // 通知 已经接受到消息相应 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self]; }); } else { // 获取状态码 NSUInteger code = [((NSHTTPURLResponse *)response) statusCode]; //This is the case when server returns '304 Not Modified'. It means that remote image is not changed. //In case of 304 we need just cancel the operation and return cached image from the cache. // 如果为304 服务端图片没有修改 则取消内部相关操作 if (code == 304) { [self cancelInternal]; } else { // 取消网络请求图片 [self.connection cancel]; } // 通知 停止下载图片 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; }); // 完成block回调 下载错误 if (self.completedBlock) { self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES); } CFRunLoopStop(CFRunLoopGetCurrent()); [self done]; } }
/** * 接收到数据 * * @param connection <#connection description#> * @param data <#data description#> */ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 追加数据 [self.imageData appendData:data]; if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) { // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/ // Thanks to the author @Nyx0uf
// Get the total bytes downloaded // 获取已下载的图片大小 const NSInteger totalSize = self.imageData.length;
// Update the data source, we must pass ALL the data, not just the new bytes // 更新数据源,我们必须传入所有的数据 并不是这次接受到的新数据 CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL); // 如果宽和高都为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); val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth); if (val) CFNumberGetValue(val, kCFNumberLongType, &width); val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue); CFRelease(properties);
// When we draw to Core Graphics, we lose orientation information, // which means the image below born of initWithCGIImage will be // oriented incorrectly sometimes. (Unlike the image born of initWithData // in connectionDidFinishLoading.) So save it here and pass it on later. // 当我们绘制 Core Graphics 时,我们将会失去图片方向的信息,这意味着有时候由initWithCGIImage方法所创建的图片的方向会不正确(不像在 connectionDidFinishLoading 代理方法里面 用 initWithData 方法创建),所以我们先在这里保存这个信息并在后面使用。 orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)]; }
if (self.connection) { // 进度block回调 if (self.progressBlock) { self.progressBlock(0, NSURLResponseUnknownLength); } // 通知 开始下载 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; }); // 开始运行 runloop if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) { // Make sure to run the runloop in our background thread so it can process downloaded data // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5 // not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466) 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对象不存在 回调相关错误信息 if (self.completedBlock) { self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES); } }
/// 管理操作类型 typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /** * By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying. * This flag disable this blacklisting. */ // 默认情况下,当一个URL下载失败后,这个URL将会加入黑名单,下次不会尝试下载。这个标志可以不让它加入黑名单 SDWebImageRetryFailed = 1 << 0,
/** * By default, image downloads are started during UI interactions, this flags disable this feature, * leading to delayed download on UIScrollView deceleration for instance. */ // 默认情况下,图片在UI交互的时候下载,这个标志可以取消这个特点。下载图片延时到UIScrollView减速的时候 SDWebImageLowPriority = 1 << 1,
/** * This flag disables on-disk caching */ // 只存储在内存 不存储在磁盘 SDWebImageCacheMemoryOnly = 1 << 2,
/** * This flag enables progressive download, the image is displayed progressively during download as a browser would do. * By default, the image is only displayed once completely downloaded. */ // 让图片逐渐的下载,在下载过程中图片像浏览器一样逐渐的显示出来。默认情况下,图片在下载完成以后才显示出来。 SDWebImageProgressiveDownload = 1 << 3,
/** * Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed. * The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation. * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics. * If a cached image is refreshed, the completion block is called once with the cached image and again with the final image. * * Use this flag only if you can't make your URLs static with embedded cache busting parameter. */ // 尽管图片有缓存,但是希望能够响应缓存控制,并且在需要的情况下从服务端刷新图片。 磁盘缓存将会被NSURLCache处理,而不是SDWebImage处理,因为SDWebImage将会带来轻微的性能下降。这个操作能够在相同URL后面帮助改变图片,像Facebook的profile pics一样。如果缓存图片被刷新了,那完成block将会用被缓存的图片调用一次,并在图片下载完后再调用一次。使用该标志只有在你需要刷新缓存图片的时候 SDWebImageRefreshCached = 1 << 4,
/** * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * extra time in background to let the request finish. If the background task expires the operation will be cancelled. */ // 后台请求 SDWebImageContinueInBackground = 1 << 5,
/** * Handles cookies stored in NSHTTPCookieStore by setting * NSMutableURLRequest.HTTPShouldHandleCookies = YES; */ SDWebImageHandleCookies = 1 << 6,
/** * Enable to allow untrusted SSL certificates. * Useful for testing purposes. Use with caution in production. */ SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/** * By default, image are loaded in the order they were queued. This flag move them to * the front of the queue and is loaded immediately instead of waiting for the current queue to be loaded (which * could take a while). */ SDWebImageHighPriority = 1 << 8, /** * By default, placeholder images are loaded while the image is loading. This flag will delay the loading * of the placeholder image until after the image has finished loading. */ // 在默认情况下,占位符图片在图片下载的时候就会被加载。使用这个标志,将会延时加载占位符图片直到图片下载成功。 SDWebImageDelayPlaceholder = 1 << 9,
/** * We usually don't call transformDownloadedImage delegate method on animated images, * as most transformation code would mangle it. * Use this flag to transform them anyway. */ // 我们平常不调用动画图片的 transformDownloadedImage 代理方法,大部分的转换代码会损坏它,使用这个标志能在任何情况下转换它们 SDWebImageTransformAnimatedImage = 1 << 10, /** * By default, image is added to the imageView after download. But in some cases, we want to * have the hand before setting the image (apply a filter or add it with cross-fade animation for instance) * Use this flag if you want to manually set the image in the completion when success */ // 默认情况下,图片下载完成以后就添加到imageview上。但是在某些情况下,我们想着设置图片之前做一些操作(比如增加一些动画什么之类的)如果你想图片下载成功后在完成block里面手动设置图片,那就使用这个标志 SDWebImageAvoidAutoSetImage = 1 << 11 };
/** * Controls which image should be downloaded when the image is not found in the cache. * * @param imageManager The current `SDWebImageManager` * @param imageURL The url of the image to be downloaded * * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied. */ /// 在图片没有在缓存里的时候,控制图片是否下载。 返回NO,如果图片没有在缓存里面的时候不下载图片。如果该方法没有被实现,那就为YES。 - (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/** * Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory. * NOTE: This method is called from a global queue in order to not to block the main thread. * * @param imageManager The current `SDWebImageManager` * @param image The image to transform * @param imageURL The url of the image to transform * * @return The transformed image object. */ /// 允许马上转换图片在图片下载完成之后,并在在缓存图片之前。注意,该方法在全局队列里面完成 为了不堵塞主线程。 - (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
/** * 下载管理图片 * * @param url URL * @param options 操作选项 * @param progressBlock 进度block * @param completedBlock 完成block * * @return <#return value description#> */ - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock { // Invoking this method without a completedBlock is pointless NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString. // 一个非常普通的错误就是把NSString对象当做NSURL来使用,但是Xcode却不能提示这种类型的错误。所以这里做一下处理 if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; }
return; } // 没有图片 或者 操作类型会刷新缓存 并且 delegate允许下载图片 if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { // 有图片 并且操作类型为 SDWebImageRefreshCached (先用缓存图片回调block 图片下载完成后再回调block) if (image && options & SDWebImageRefreshCached) { dispatch_main_sync_safe(^{ // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. completedBlock(image, nil, cacheType, YES, url); }); }
// download if no image or requested to refresh anyway, and download allowed by delegate // 根据SDWebImageOptions的类型 得到SDWebImageDownloaderOptions 都是一些位运算 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) { // force progressive off if image already cached but forced refreshing downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } // 下载图片 id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { if (weakOperation.isCancelled) { // Do nothing if the operation was cancelled // See #699 for more details // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data // 操作被取消 则不做任何操作 } else if (error) { // 出错 dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(nil, error, SDImageCacheTypeNone, finished, url); } });
为什么从磁盘里面取出图片后,block回调之前要解压图片呢?因为图片在ImageView上面显示的时候需要解压,而这个解压操作是在主线程里面进行的,比较耗时,这样就会产生延时效果,而用decodedImageWithImage:在后台解压能够解决这一问题,但是这种用空间换时间的方法也存在着内存暴增甚至崩溃等问题,所以自己得权衡一下。这就是为什么SDImageCache、SDWebImageDownloader、SDWebImageDownloaderOperation类中都有shouldDecompressImages (是否解压图片)值存在的原因。 关于图片解压的问题,可以看Avoiding Image Decompression Sickness这篇博客。解压图片而产生的相关问题,以及解决方案的由来可以看How to solve the memory consumption for the decodedImageWithImage这个issue。