上篇讲了SDWebImageDownloader,从源码分析的过程中,我们知道,实际执行下载任务的是SDWebImageDownloaderOperation,本篇我们来看看SDWebImageDownloaderOperation是怎么工作。

在正式讲SDWebImageDownloaderOperation之前,我们有必要对SDWebImageDownloaderOperation的父类NSOperation做一个简单的了解。

1.NSOperation

我们知道,为了更好的提高用户体验,避免卡顿现象的发生,我们一般采用多线程的方式来进行用户体验上的提升。GCD、NSThread、NSOperation都能实现多线程。关于NSOperation的详细介绍,大家可以看下这篇文章:多线程之NSOperation简介,这里只做个简单的总结。

  • NSOperation有两个方法:main() 和 start()。如果想使用同步,那么最简单方法的就是把逻辑写在main()中,使用异步,需要把逻辑写到start()中,然后加入到队列之中。
  • NSOperation什么时候执行呢?按照正常想法,是手动调用main() 和 start(),当然这样也可以。当调用start()的时候,默认的是在当前线程执行同步操作,如果是在主线程调用了,那么必然会导致程序死锁。另外一种方法就是加入到operationQueue中,operationQueue会尽快执行NSOperation,如果operationQueue是同步的,那么它会等到NSOperation的isFinished等于YES后,再执行下一个任务,如果是异步的,通过设置maxConcurrentOperationCount来控制同时执行的最大操作,某个操作完成后,继续其他的操作。
  • 并不是调用了cancel就一定取消了,如果NSOperation没有执行,那么就会取消,如果执行了,只会将isCancelled设置为YES。所以,在我们的操作中,我们应该在每个操作开始前,或者在每个有意义的实际操作完成后,先检查下这个属性是不是已经设置为YES。如果是YES,则后面操作都可以不用再执行了。

2.通知

extern NSString * _Nonnull const SDWebImageDownloadStartNotification;  //!<任务开始
extern NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification; //!<接收到数据
extern NSString * _Nonnull const SDWebImageDownloadStopNotification; //!<暂停
extern NSString * _Nonnull const SDWebImageDownloadFinishNotification; //!<完成

3.SDWebImageDownloaderOperationInterface协议

@protocol SDWebImageDownloaderOperationInterface<NSObject>

/**
初始化方法 @param request 请求
@param session NSURLSession,用来执行下载任务
@param options 下载选项
@return self
*/
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options;
/**
给Operation添加进度和回调Block @param progressBlock 进度Block
@param completedBlock 回调Block
@return 回调字典
*/
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; //是否需要解码
- (BOOL)shouldDecompressImages;
- (void)setShouldDecompressImages:(BOOL)value; //是否需要设置凭证
- (nullable NSURLCredential *)credential;
- (void)setCredential:(nullable NSURLCredential *)value; @end

在这个协议的声明中,作者有一段注释内容:

/**
Describes a downloader operation. If one wants to use a custom downloader op, it needs to inherit from `NSOperation` and conform to this protocol
*/

意思是如果我们想要实现一个自定义的下载操作,就必须继承自NSOperation,同时实现SDWebImageDownloaderOperationInterface这个协议。

4.SDWebImageDownloaderOperation的属性

//.h文件

@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;  //!<操作任务使用的请求

@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;  //!<操作任务

@property (assign, nonatomic) BOOL shouldDecompressImages;  //!<是否需要解码(来源于协议SDWebImageDownloaderOperationInterface)

@property (nonatomic, strong, nullable) NSURLCredential *credential;  //!<是否需要设置凭证(来源于协议SDWebImageDownloaderOperationInterface)

@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;  //!<SDWebImageDownloaderOptions选项

@property (assign, nonatomic) NSInteger expectedSize;  //!<总大小

@property (strong, nonatomic, nullable) NSURLResponse *response;  //!<响应对象

//.m文件
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks; //!<回调Block列表 @property (assign, nonatomic, getter = isExecuting) BOOL executing; //!<自定义并行Operation需要管理的两个属性
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic, nullable) NSMutableData *imageData; //!<存储图片数据 // This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
// the task associated with this operation
@property (weak, nonatomic, nullable) NSURLSession *unownedSession; //!<通过SDWebImageDownloader传过来,所以这里是weak。因为它是通过SDWebImageDownloader管理的
// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
@property (strong, nonatomic, nullable) NSURLSession *ownedSession; //!<如果unownedSession是nil,我们需要手动创建一个并且管理它的生命周期和代理方法 @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask; //!<dataTask对象 @property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue; //!<一个并行queue,用于控制数据的处理 #if SD_UIKIT
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; //!<如果用户设置了后台继续加载选项,则通过backgroundTask来继续下载图片
#endif

5.SDWebImageDownloaderOperation的方法

在本篇的开始提到,SDWebImageDownloaderOperation用于执行实际的下载任务。那么执行下载任务的逻辑包含哪几部分内容呢?大致有如下四个:

  1. 初始化任务;
  2. 添加响应者;
  3. 开始下载任务;
  4. 处理下载过程和结束的结果。

下面我们从这四个部分来分析一下SDWebImageDownloaderOperation的相关方法。

5.1初始化任务

- (nonnull instancetype)init {
return [self initWithRequest:nil inSession:nil options:];
} - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = ;
_unownedSession = session;
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}

这里主要是进行一些属性的初始化配置。

5.2添加响应者

/**
给Operation添加进度和回调Block @param progressBlock 进度Block
@param completedBlock 回调Block
@return 回调字典
*/
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
//把Operation对应的回调和进度Block存入一个字典中
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
//把完成和进度Block加入callbackBlocks中
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
} /**
根据key取出所有符合key的block @param key key
@return 符合key的block
*/
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
__block NSMutableArray<id> *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{
// We need to remove [NSNull null] because there might not always be a progress block for each callback
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
return [callbacks copy]; // strip mutability here
} /**
取消某一回调 @param token 和addHandlersForProgress:completed:方法的返回值对应
@return YES/NO
*/
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == ) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}

方法职责比较清晰,可能大家会对标红色的两个方法以及这几个方法的数据结构不太理解,这里写一个Demo来演示一下效果。

//变量定义
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed"; //属性定义
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks; //!<回调Block列表
@property (strong, nonatomic, nullable) dispatch_queue_t barrierQueue; //!<一个并行queue,用于控制数据的处理 //方法定义
/**
给Operation添加进度和回调Block @param progressBlock 进度Block
@param completedBlock 回调Block
@return 回调字典
*/
- (nullable id)addHandlersForProgress:(NSString *)progressBlock
completed:(NSString *)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
//把Operation对应的回调和进度Block存入一个字典中
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
//把完成和进度Block加入callbackBlocks中
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
NSLog(@"addHandlersForProgress callbackBlocks : \r\n%@", self.callbackBlocks);
NSLog(@"addHandlersForProgress callbacks : \r\n%@", callbacks);
}); return callbacks;
} /**
根据key取出所有符合key的block @param key key
@return 符合key的block
*/
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
__block NSMutableArray<id> *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{ callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
NSLog(@"callbacksForKey callbackBlocks : \r\n%@", self.callbackBlocks);
NSLog(@"callbacksForKey Before Remove Null callbacks : \r\n%@", callbacks);
[callbacks removeObjectIdenticalTo:[NSNull null]];
NSLog(@"callbacksForKey After Remove Null callbacks : \r\n%@", callbacks);
});
return [callbacks copy]; // strip mutability here
} /**
取消某一回调 @param token 和addHandlersForProgress:completed:方法的返回值对应
@return YES/NO
*/
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
dispatch_barrier_sync(self.barrierQueue, ^{
NSLog(@"cancel Before Remove callbackBlocks : \r\n%@", self.callbackBlocks); [self.callbackBlocks removeObjectIdenticalTo:token]; NSLog(@"cancel After Remove callbackBlocks : \r\n%@", self.callbackBlocks);
if (self.callbackBlocks.count == ) {
shouldCancel = YES;
}
});
return shouldCancel;
} //调用
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_callbackBlocks = [NSMutableArray new]; [self addHandlersForProgress:@"AA" completed:@""];
[self addHandlersForProgress:@"BB" completed:@""];
//手动加一条
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
callbacks[@"CC"] = @"";
callbacks[@"DD"] = @"";
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
NSLog(@"手动加 callbackBlocks : \r\n%@", self.callbackBlocks);
NSLog(@"手动加 callbacks : \r\n%@", callbacks);
}); [self callbacksForKey:kProgressCallbackKey];
[self cancel:[self.callbackBlocks objectAtIndex:]]; //打印结果
/*
addHandlersForProgress callbackBlocks :
(
{
completed = 11;
progress = AA;
}
)
addHandlersForProgress callbacks :
{
completed = 11;
progress = AA;
}
addHandlersForProgress callbackBlocks :
(
{
completed = 11;
progress = AA;
},
{
completed = 22;
progress = BB;
}
)
addHandlersForProgress callbacks :
{
completed = 22;
progress = BB;
}
手动加 callbackBlocks :
(
{
completed = 11;
progress = AA;
},
{
completed = 22;
progress = BB;
},
{
CC = 33;
DD = 44;
}
)
手动加 callbacks :
{
CC = 33;
DD = 44;
}
callbacksForKey callbackBlocks :
(
{
completed = 11;
progress = AA;
},
{
completed = 22;
progress = BB;
},
{
CC = 33;
DD = 44;
}
)
callbacksForKey Before Remove Null callbacks :
(
AA,
BB,
"<null>"
)
callbacksForKey After Remove Null callbacks :
(
AA,
BB
)
cancel Before Remove callbackBlocks :
(
{
completed = 11;
progress = AA;
},
{
completed = 22;
progress = BB;
},
{
CC = 33;
DD = 44;
}
)
cancel After Remove callbackBlocks :
(
{
completed = 22;
progress = BB;
},
{
CC = 33;
DD = 44;
}
)
*/

另外,在本节还需要留意一个知识点:dispatch_barrier_async,该API用于拦截任务,只有当前任务执行完成之后,后面的任务才能继续执行,详情可参看GCD浅析

5.3开始下载任务

/**
并行的Operation需要重写这个方法,在这个方法里面做具体的处理
*/
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
} #if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
//如果用户设置了Background模式,则设置一个backgroundTask
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
//background结束以后,做清理工作
if (sself) {
[sself cancel]; [app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
//如果SDWebImageDownloader传入的session是nil,则自己手动初始化一个
if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = ; /**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
} self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
//发送请求
[self.dataTask resume]; if (self.dataTask) {
//第一次调用进度BLOCK
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(, NSURLResponseUnknownLength, self.request.URL);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code: userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
} #if SD_UIKIT
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
} /**
如果要取消一个Operation,就会调用这个方法
*/
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
} - (void)cancelInternal {
if (self.isFinished) return;
[super cancel]; if (self.dataTask) {
[self.dataTask cancel];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
}); // As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
//更新状态
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
} [self reset];
} /**
下载完成
*/
- (void)done {
self.finished = YES;
self.executing = NO;
[self reset];
} /**
如果任务已经被设置为取消了,那么就无需开启下载任务了,并进行重置
*/
- (void)reset {
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
});
self.dataTask = nil;
self.imageData = nil;
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
} /**
需要手动触发_finished的KVO,这个是自定义并发`NSOperation`必须实现的 @param finished 改变状态
*/
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
} /**
需要手动触发_executing的KVO,这个是自定义并发`NSOperation`必须实现的 @param executing 改变状态
*/
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
} /**
返回YES,表明这个NSOperation对象是并发的 @return YES
*/
- (BOOL)isConcurrent {
return YES;
}

5.4处理下载过程和结束的结果

#pragma mark NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { //'304 Not Modified' is an exceptional one
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < && ((NSHTTPURLResponse *)response).statusCode != )) {
//期望的总长度
NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > ? expected : ;
self.expectedSize = expected;
//进度回调Block
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(, expected, self.request.URL);
} self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
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表示图片没有变化,在这种情况下,我们只需要取消operation并且返回缓存的图片就可以了
if (code == ) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
}); [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]]; [self done];
}
//这个表示允许继续加载
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
} /**
会被多次调用,获取图片数据 @param session session
@param dataTask dataTask
@param data data
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.imageData appendData:data]; if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > ) {
// 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);
//width和height都是0的话表示还么有获取到图片的高度和宽度,我们可以通过数据来获取图片的宽度和高度。此时表示第一次收到图片数据
if (width + height == ) {
//获取图片数据的属性
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, , NULL);
if (properties) {
NSInteger orientationValue = -;
//获取高度值
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 didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
orientation = [[self class] orientationFromPropertyValue:(orientationValue == - ? : orientationValue)];
#endif
}
}
//这个表示已经收到部分图片数据并且还没有获取到所有的图片数据
if (width + height > && totalSize < self.expectedSize) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, , NULL); #if SD_UIKIT || SD_WATCH
// Workaround for iOS anamorphic image
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, , width * , 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) {
#if SD_UIKIT || SD_WATCH
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale: orientation:orientation];
#elif SD_MAC
UIImage *image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif
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); [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
} CFRelease(imageSource);
} for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
}
} /**
用于响应缓存设置,如果把回调的参数设置为nil,那么就不会缓存响应 @param session session
@param dataTask dataTask
@param proposedResponse proposedResponse
@param completionHandler 回调
*/
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
//根据request的选项,决定是否缓存NSCachedURLResponse
NSCachedURLResponse *cachedResponse = proposedResponse; if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
} #pragma mark NSURLSessionTaskDelegate /**
网络请求加载完成,在这里处理获得的数据 @param session session
@param task task
@param error error
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
//发送图片下载完成的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
}
});
} if (error) {
[self callCompletionBlocksWithError:error];
} else {
if ([self callbacksForKey:kCompletedCallbackKey].count > ) {
/**
* If you specified to use `NSURLCache`, then the response you get here is what you need.
* if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
* the response data will be nil.
* So we don't need to check the cache option here, since the system will obey the cache option
*/
if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
//获取url对应的缓存Key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image]; // Do not force decoding animated GIFs
if (!image.images) {
//是否解码图片数据
if (self.shouldDecompressImages) {
//如果设置了SDWebImageDownloaderScaleDownLargeImages,则返回处理过的图片
if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
#if SD_UIKIT || SD_WATCH
image = [UIImage decodedAndScaledDownImageWithImage:image];
[self.imageData setData:UIImagePNGRepresentation(image)];
#endif
} else {
image = [UIImage decodedImageWithImage:image];
}
}
}
//构建回调Block
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code: userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:self.imageData error:nil finished:YES];
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code: userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
[self done];
} /**
验证HTTPS的证书 @param session session
@param task task
@param challenge challenge
@param completionHandler 回调
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
//使用可信任证书机构的证书
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//如果SDWebImageDownloaderAllowInvalidSSLCertificates属性设置了,则不验证SSL证书,直接信任
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
//使用自己生成的证书
if (challenge.previousFailureCount == ) {
if (self.credential) {
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
//验证证书
if (completionHandler) {
completionHandler(disposition, credential);
}
}

5.5其他方法

/**
把整数转换为对应的枚举值 @param value 整数值
@return 枚举值
*/
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
switch (value) {
case :
return UIImageOrientationUp;
case :
return UIImageOrientationDown;
case :
return UIImageOrientationLeft;
case :
return UIImageOrientationRight;
case :
return UIImageOrientationUpMirrored;
case :
return UIImageOrientationDownMirrored;
case :
return UIImageOrientationLeftMirrored;
case :
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
}
}
#endif /**
通过image对象获取对应scale模式下的图像 @param key key
@param image image
@return 图像
*/
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
} - (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
} - (void)callCompletionBlocksWithError:(nullable NSError *)error {
[self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
} /**
处理回调 @param image UIImage数据
@param imageData Image的data数据
@param error 错误
@param finished 是否完成的标记位
*/
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
//获取key对应的回调Block数组
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
//调用回调
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}

SDWebImage之SDWebImageDownloaderOperation的更多相关文章

  1. SDWebImage源码分析

    1.概述 SDWebImage是iOS开发中,被广泛使用的一个第三方开源库,提供了图片从加载.解析.处理.缓存.清理等一些列功能,让我们能够专心于业务的处理.本篇会从SDWebImage的源码,来一步 ...

  2. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  3. Xcode6编译SDWebImage报错解决方法(SDWebImageDownloaderOperation.m错误)

    报错:Use of undeclared identifier '_executing' / '_finished': 解决方法: 在SDWebImageDownloaderOperation类的实现 ...

  4. xcode6 中增加SDWebImage/SDWebImageDownloaderOperation.m报错解决方法

    报错报错:Use of undeclared identifier '_executing' / '_finished': 解决方法例如以下:

  5. 【原】SDWebImage源码阅读(三)

    [原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们 ...

  6. 【原】SDWebImage源码阅读(二)

    [原]SDWebImage源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 解决上一篇遗留的坑 上一篇中对sd_setImageWithURL函数简单分析了一下,还 ...

  7. 通读SDWebImage①--总体梳理、下载和缓存

    本文目录 下载操作SDWebImageDownloaderOptions和下载过程实现 下载管理SDWebImageDownloader 缓存SDImageCache SDWebImageManage ...

  8. 解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题

    下面两种现象,用同一种方法解决 1.解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题 2.突然有一天首页访问图片很慢,至少隔20多秒所有图片才会出来.(解析:app使 ...

  9. IOS 使用SDWebImage实现仿新浪微博照片浏览器

    使用第三方库SDWebImage实现仿新浪微博照片浏览器,可以下载图片缓存,点击之后滚动查看相片,具体效果如下: 代码如下: WeiboImageView.h: #import <UIKit/U ...

随机推荐

  1. Creating adaptive web recommendation system based on user behavior(设计基于用户行为数据的适应性网络推荐系统)

    文章介绍了一个基于用户行为数据的推荐系统的实现步骤和方法.系统的核心是专家系统,它会根据一定的策略计算所有物品的相关度,并且将相关度最高的物品序列推送给用户.计算相关度的策略分为两部分,第一部分是针对 ...

  2. python 的深浅copy

    1.引用: A=B, 修改B后,A会被修改 2.浅拷贝:A=copy.copy(B) 3.深拷贝:A=copy.deepcopy(B) 如果希望任何改变,两个对象都不会相互影响,用深拷贝.详情参考:h ...

  3. 数据库设计,表与表的关系,一对多。One-To-Many(2)

    一对多:主键数据表中只能包含一个记录,而在其关系记录表中这条记录可以与一个或多个记录相关,也可以没有记录与之相关. 关联映射:一对多/多对一存在最普遍的映射关系,简单来讲就如球员与球队的关系:一对多: ...

  4. mysql简单介绍及安装

    MySQL是一个关系型数据库管理系统关系数据库,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性,所使用的 SQL 语言是用于访问数据库的最常用标准化语言.My ...

  5. git command line 提交代码

    echo "# spring-boot-apollo-demo" >> README.md git init git add README.md git commit ...

  6. prometheus 表达式

    avg_over_time(my_inprogress_requests{job="mhc"}[5m] offset 3m) 返回time=1550664637开始向前偏移3分钟之 ...

  7. 【Django】 TemplateDoesNotExist at /HTMLeditor/HTMLeditorHandler/

    TemplateDoesNotExist at /HTMLeditor/HTMLeditorHandler/search/indexes/htmleditor/htmleditor_text.txt ...

  8. windows、Linux同步外网NTP服务器时间

    配置 Windows 时间服务以使用外部时间源 要将内部时间服务器配置为与外部时间源同步,请使用以下方法之一: 软件自动配置  Windows 时间服务 若要自动修复此问题,请单击“下载”按钮. 在“ ...

  9. FortiGate日志中session clash

    1.出现于:FortiGate v5.0和v5.2 2.出现原因 Session clash messages appear in the logs when a new session is cre ...

  10. tar: Removing leading `/' from member names

    解决办法使用 -P 参数 注意 -f 参数后面跟压缩后的文件名