SDWebImage之SDWebImageDownloaderOperation

上篇讲了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:0];
}

- (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 = 0;
        _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 == 0) {
            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 == 0) {
            shouldCancel = YES;
        }
    });
    return shouldCancel;
}

//调用
    _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
    _callbackBlocks = [NSMutableArray new];

    [self addHandlersForProgress:@"AA" completed:@"11"];
    [self addHandlersForProgress:@"BB" completed:@"22"];
    //手动加一条
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    callbacks[@"CC"] = @"33";
    callbacks[@"DD"] = @"44";
    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:0]];

//打印结果
/*
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 = 15;

            /**
             *  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(0, NSURLResponseUnknownLength, self.request.URL);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    } else {
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 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 < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
        //期望的总长度
        NSInteger expected = (NSInteger)response.expectedContentLength;
        expected = expected > 0 ? expected : 0;
        self.expectedSize = expected;
        //进度回调Block
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, 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 == 304) {
            [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 > 0) {
        // 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 == 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 didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
                orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
#endif
            }
        }
        //这个表示已经收到部分图片数据并且还没有获取到所有的图片数据
        if (width + height > 0 && totalSize < self.expectedSize) {
            // Create the image
            CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, 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, 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) {
#if SD_UIKIT || SD_WATCH
                UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 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 > 0) {
            /**
             *  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:0 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:0 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 == 0) {
            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 1:
            return UIImageOrientationUp;
        case 3:
            return UIImageOrientationDown;
        case 8:
            return UIImageOrientationLeft;
        case 6:
            return UIImageOrientationRight;
        case 2:
            return UIImageOrientationUpMirrored;
        case 4:
            return UIImageOrientationDownMirrored;
        case 5:
            return UIImageOrientationLeftMirrored;
        case 7:
            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);
        }
    });
}

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo; color: #6122ae }
span.s1 { }

时间: 2024-11-01 03:28:08

SDWebImage之SDWebImageDownloaderOperation的相关文章

Xcode6编译SDWebImage报错原因分析(SDWebImageDownloaderOperation.m错误)

之前写了一片关于编译SDWebImage报错解决方法的文章: http://blog.csdn.net/cuibo1123/article/details/39434015 结果很多人问这个问题的原因,那就在这里给大家说一说好了,分析思路一并送上(不过我还是建议大家自己动手去分析). 首先,如果新建工程,按照SDWebImage的方式声明输出口并引用: @interface ZCTest () @property (assign, nonatomic, getter = isExecuting)

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

报错:Use of undeclared identifier '_executing' / '_finished': 解决方法: 在SDWebImageDownloaderOperation类的实现中(@implementation里)添加: 1 @synthesize executing = _executing; 2 @synthesize finished = _finished; 即可.

SDWebImage源码阅读(七)SDWebImageDownloaderOperation

SDWebImageDownloaderOperation 继承自 NSOperation. 这里首先学习 NSOperation 类: 在 iOS 开发中,通常会把比较耗时的操作放在主线程之外的子线程里面去执行.而开辟子线程基本都是使用 API 相对简单易懂的 GCD 去操作,把所有的操作代码都放在 block 里面去书写,相对于其它开辟子线程的方法大大降低了开发难度.当然会使用和知道其底层原理是有天壤之别的.NSOperation 在 iOS 4 后也是基于 GCD 实现的.

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

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

李洪强详细介绍SDWebImage

SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能.它具有以下功能: 提供UIImageView的一个分类,以支持网络图片的加载与缓存管理 一个异步的图片加载器 一个异步的内存+磁盘图片缓存 支持GIF图片 支持WebP图片 后台图片解压缩处理 确保同一个URL的图片不被下载多次 确保虚假的URL不会被反复加载 确保下载及缓存时,主线程不被阻塞 从github上对SDWebImage使用情况就可以看出,SDWebImage在

SDWebImage源码学习笔记

//  这是我第二次学习sdwebimage源码,第一次学习吸收的很少,看不懂啊.第二次看个50%,在此记录一点笔记. 首先是目录: 1.SDWebImage目录 里面有两个类,SDWebImageCompat.h 里面有个根据屏幕设置图片scale的方法 SDWebImageOperation.h 声明了一个协议,取消操作 (可以理解这一个放的公共方法目录) 2.Downloader 目录(顾名思义,下载操作相关的目录)里面有两个关键的类 SDWebImageDownloaderOperati

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

解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题 iOS开发 · 2015-01-22 11:31 MWPhotoBrowser是一个非常不错的照片浏览器,在github的star接近3000个,地址:https://github.com/mwaterfall/MWPhotoBrowser.git MWPhotoBrowser来加载小图1M以下的都应该不会有内存警告的问题.如果遇到大图,3M.4M.5M的大图,很有可能导致内存警告.最近我就遇到这个问题,很是头疼

通读SDWebImage源码

SDWebImage是iOS开发者经常使用的一个开源框架,这个框架的主要作用是:一个异步下载图片并且支持缓存的UIImageView分类. #import <Foundation/Foundation.h> #import "SDWebImageCompat.h" typedef NS_ENUM(NSInteger, SDImageCacheType) { /** * The image wasn't available the SDWebImage caches, but

你真的懂SDWebImage?

源码来源:  https://github.com/rs/SDWebImage 版本: 3.7 SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能.它具有以下功能: 提供UIImageView的一个分类,以支持网络图片的加载与缓存管理 一个异步的图片加载器 一个异步的内存+磁盘图片缓存 支持GIF图片 支持WebP图片 后台图片解压缩处理 确保同一个URL的图片不被下载多次 确保虚假的URL不会被反复加载 确保下载及缓存时,