SDWebImage源码详解 - 异步下载器SDWebImageDownloader

SDWebImage源码详解 - 异步下载器SDWebImageDownloader

SDWebImage的图片下载是由SDWebImageDownloader这个类来实现的,它是一个异步下载管理器,下载过程中增加了对图片加载做了优化的处理。而真正实现图片下载的是自定义的一个Operation操作,将该操作加入到下载管理器的操作队列downloadQueue中,Operation操作依赖系统提供的NSURLConnection类实现图片的下载。
我们来看一看这个异步下载器SDWebImageDownloader的具体实现:

//在这里由枚举定义了不同的下载选项在下载的过程中,程序会根据不同的下载选项,而执行不同的操作
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    SDWebImageDownloaderProgressiveDownload = 1 << 1,

    //默认情况下请求不使用NSURLCache,如果设置该选项,则以默认的缓存策略来使用NSURLCache
    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    //如果从NSURLCache缓存中读取图片,则使用nil作为参数来调用完成block
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    
    // 在iOS 4+系统上,允许程序进入后台后继续下载图片。该操作通过向系统申请额外的时间来完成后台下载。如果后台任务终止,则操作会被取消
    SDWebImageDownloaderContinueInBackground = 1 << 4,

    // 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES来处理存储在NSHTTPCookieStore中的cookie
    SDWebImageDownloaderHandleCookies = 1 << 5,

    // 允许不受信任的SSL证书。主要用于测试目的
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    // 将图片下载放到高优先级队列中
    SDWebImageDownloaderHighPriority = 1 << 7,
};


//下载管理器还提供了两种下载顺序,以控制下载操作的顺序
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
    // 以队列的方式,按照先进先出的顺序下载。这是默认的下载顺序
    SDWebImageDownloaderFIFOExecutionOrder,

    // 以栈的方式,按照后进先出的顺序下载。
    SDWebImageDownloaderLIFOExecutionOrder
};

//外部定义的系统通知标示
extern NSString *const SDWebImageDownloadStartNotification;
extern NSString *const SDWebImageDownloadStopNotification;

//每个下载操作都定义了回调操作,如下载进度回调,下载完成回调,头部过滤等,这些回调操作是以block形式来呈现
//每个下载操作的下载进度回调和下载完成回调,这两个回调稍后将保存在下载管理器的URLCallbacks字典中,key为URL,value为一个数组,数组里面又存放一个保存了下载进度回调和完成回调代码块的字典。
//这个字典数组同时也保证了同一张图片只会被下载一次
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);

typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);

typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);

@interface SDWebImageDownloader : NSObject

//是否解压图片,默认为YES,此项操作可以提升性能,但是需要消耗较多内存
@property (assign, nonatomic) BOOL shouldDecompressImages;

//队列最大并发数
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;

//当前正在或者待执行的下载任务数
@property (readonly, nonatomic) NSUInteger currentDownloadCount;


//每个下载操作的超时时间
@property (assign, nonatomic) NSTimeInterval downloadTimeout;



//下载操作的执行顺序,默认是SDWebImageDownloaderFIFOExecutionOrder先进先出顺序
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;

//下载管理器单例函数
+ (SDWebImageDownloader *)sharedDownloader;


//设置下载操作的请求凭证
@property (strong, nonatomic) NSURLCredential *urlCredential;

//设置用户名
@property (strong, nonatomic) NSString *username;

//设置密码
@property (strong, nonatomic) NSString *password;

//http头部的过滤函数
@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;

//设置http请求头部字段
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;

//返回http请求头部字段
- (NSString *)valueForHTTPHeaderField:(NSString *)field;


//设置图片下载的操作。默认使用SDWebImageDownloaderOperation这个自定义的下载操作
- (void)setOperationClass:(Class)operationClass;


//创建一个异步下载器,
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageDownloaderOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageDownloaderCompletedBlock)completedBlock;

//暂停下载队列
- (void)setSuspended:(BOOL)suspended;

下载管理器的主要实现为downloadImageWithURL:options:progress:completed:方法,这个方法调用- (void)addProgressCallback:completedBlock:forURL: createCallback:方法来将请求的信息和一些回调函数存入管理器中,同时在创建回调的block中创建新的operation操作,新的操作由管理器中存储的信息配置后,放入到downloadQueue操作队列中,最后返回新创建的操作。接下来看一下这个函数的具体实现:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;
    
    
    //调用`downloadImageWithURL:options:progress:completed:`方法创建operation操作
    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
        //下载操作的超时时间.默认是15.0S
        NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }

        //创建请求对象,并根据options参数设置其属性
        //为了避免潜在的重复缓存(NSURLCache + SDImageCache),如果没有明确告知需要缓存,则禁用图片请求的缓存操作
        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;
        }
        //创建SDWebImageDownloaderOperation操作对象,并进行配置
        //配置信息包括是否需要认证、优先级
        operation = [[wself.operationClass alloc] initWithRequest:request
                                                          options:options
                                                          //从管理器的callbacksForURL中找出该URL所有的进度处理回调并调用
                                                         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);
                                                                 });
                                                             }
                                                         }
                                                         //从管理器的callbacksForURL中找出该URL所有的完成处理回调并调用,
                                                           // 如果finished为YES,则将该url对应的回调信息从URLCallbacks中删除
                                                        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);
                                                            }
                                                        }
                                                        //取消操作将该url对应的回调信息从URLCallbacks中删除
                                                        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;
        }
        
        //加入到NSOperationQueue队列中,自动调用start函数
        //将操作加入到操作队列downloadQueue中
        // 如果是LIFO顺序,则将新的操作作为原队列中最后一个操作的依赖,然后将新操作设置为最后一个操作
        [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;
}

//这个方法主要是保存传入下载管理器的下载进度回调或完成回调,然后创建新的回调操作
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
    
    //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的同步下载请求的单个下载
        //每个下载操作的下载进度回调和下载完成回调,这两个回调稍后将保存在下载管理器的URLCallbacks字典中,
            //key为URL,value为一个数组,数组里面又存放一个保存了下载进度回调和完成回调代码块的字典。
        //这个字典数组同时也保证了同一张图片只会被下载一次
        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();
        }
    });
}
下载操作

每个图片的下载都是一个Operation操作,由下载管理器来管理每个Operation的具体执行,接下来看一下图片下载操作的具体实现,
首先SDWebImage定义了一个协议,即SDWebImageOperation,作为图片下载操作的基础协议。它只声明了一个cancel方法,用于取消操作。协议的具体声明如下:

@protocol SDWebImageOperation <NSObject>

- (void)cancel;

@end

然后SDWebImage自定义实现了一个Operation类:SDWebImageDownloaderOperation,遵循SDWebImageOperation协议,这个类对外公开了一个初始化方法:initWithRequest:options:progress:completed:cancelled:.图片的真实下载操作依赖系统提供的NSURLConnection类。可以看到SDWebImageDownloaderOperation实现了NSURLConnectionDataDelegate协议。这里不过多介绍NSURLConnection的使用,具体使用可以参看这里,稍后我们将主要介绍两个代理方法,-connection:didReceiveData:-connectionDidFinishLoading:.
#######start 方法
既然在下载管理器中使用NSOperationQueue来管理下载操作对象SDWebImageDownloaderOperation,这样我们就需要实现自定义Operation的main方法或者start方法,(关于start方法和main方法,有这么一句话【如果你要处理的非并发的操作,你需要实现-main方法,如果你要处理的是并发的操作,你需要实现-start方法】),所以SDWebImageDownloaderOperation实现了start方法,(注意:重写“start”方法需要相对复杂的实现,需要注意像isExecuting,isFinished,isConcurrent和isReady这些属性)。start方法创建了我们下载所使用的NSURLConnection对象,开启了图片的下载,同时抛出一个下载开始的通知。当然,如果我们期望下载在后台处理,则只需要配置我们的下载选项,使其包含SDWebImageDownloaderContinueInBackground选项。start方法的具体实现如下:

- (void)start {
    //管理下载状态,如果已取消,则重置当前下载并设置完成状态为YES
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

        //开启后台下载
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;
                //如果在规定时间内未完成,则执行此代码块
                if (sself) {
                    [sself cancel];

                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#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];
        });
        
        //开启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();
        }
        //线程开始的时候,connect还未结束,则取消连接
        if (!self.isFinished) {
            [self.connection cancel];
            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
    else { //连接未成功,执行错误回调
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}
代理方法

这里SDWebImageDownloaderOperation自定义操作实现了NSURLConnectionDataDelegate协议的以下几个方法:

//接收到服务器响应的时候调用这个方法,这里主要对304返回代码,做一下处理
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
//接收到服务器返回数据时调用,多次调用。这里主要对接受到的图片数据做一下处理,稍后详细介绍
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
//图片数据接受完毕时调用,调用完成回调函数处理图片
- (void)connectionDidFinishLoading:(NSURLConnection *)Connection;
//连接失败是调用
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
//设置缓存数据
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse;
//是否使用请求认证
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection
//服务端要求客户端接收认证
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

我这里主要介绍这两个两个代理方法,-connection:didReceiveData:-connectionDidFinishLoading:.

//当每次接收服务传回的数据时就会调用此方法。
//每次接收到数据时,都会用现有的数据创建一个CGImageSourceRef对象以做处理。
//在首次获取到数据时,也就是图片的长宽都为0(width+height==0),先从这些包含图像信息的数据中取出图像的长、宽、方向等信息以备使用。
//而后在图片全部下载完成之前,会使用CGImageSourceRef对象创建一个图片对象,经过缩放、解压缩操作后生成一个UIImage对象供完成回调使用。
//如果我们有设置进度回调的话,就调用这个进度回调以处理当前图片的下载进度。
- (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 imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);

        if (width + height == 0) { //刚开始时,图片宽高都为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);

                        //当绘制到Core Graphics时,我们会丢失方向信息,
                        //这意味着有时候由initWithCGIImage创建的图片的方向会不对,
                        //所以在这边我们先保存这个信息并在后面使用。
                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
            // Workaround for iOS anamorphic image
            //适用于iOS变形图像的解决方案。
                    //由于iOS只支持RGB颜色空间,所以在此对下载下来的图片做个颜色空间转换处理
            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];
                if (self.shouldDecompressImages) {
                    image = [UIImage decodedImageWithImage:scaledImage];
                }
                else {
                    image = scaledImage;
                }
                CGImageRelease(partialImageRef);
                dispatch_main_sync_safe(^{ //这里怎么会执行completedBlock?
                    if (self.completedBlock) {
                        self.completedBlock(image, nil, nil, NO);
                    }
                });
            }
        }

        CFRelease(imageSource);
    }

    if (self.progressBlock) {
        self.progressBlock(self.imageData.length, self.expectedSize);
    }
}

再来看一下-connectionDidFinishLoading:方法,当数据下载完成后,将调用此方法,方法调用的时间点,决定了这个方法主要对图片做下载完成的处理,以及操作环境的清理。

- (void)connectionDidFinishLoading:(NSURLConnection *)Connection {
    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
    //停止unloop
    @synchronized(self) {
        CFRunLoopStop(CFRunLoopGetCurrent());
        self.thread = nil;
        self.connection = nil;
        //在主线程中发送下载完成和停止的通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
        });
    }
    
    if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
        responseFromCached = NO;
    }
    
    //如果有完成回调,则处理完成回调
    if (completionBlock) {
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
            completionBlock(nil, nil, nil, YES);
        } else if (self.imageData) {
            UIImage *image = [UIImage sd_imageWithData:self.imageData];
            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
            image = [self scaledImageForKey:key image:image];
            
            // Do not force decoding animated GIFs
            //对于gif图片不做解码操作
            if (!image.images) {
                if (self.shouldDecompressImages) {
                    image = [UIImage decodedImageWithImage:image];
                }
            }
            if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
            }
            else {
                completionBlock(image, self.imageData, nil, YES);
            }
        } else {
            completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
        }
    }
    self.completionBlock = nil;
    //清理操作环境
    [self done];
}

图片下载的实现是主要是实现一个自定义的Operation操作对象,每一个图片下载对应一个<strong>操作对象</strong>,然后通过<strong>下载管理器</strong>来统一管理这些操作对象。我的理解:下载管理器的主要作用是,统一管理每个图片下载的Operation配置,针对每个自定义Operation操作,管理和设置不同的下载进度回调和完成回调,使得自定义的Operation操作只需关注单个的操作即可,降低了代码耦合。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容