第一篇第二篇大概是把下載圖片緩存圖片的這個邏輯走完了,里面涉及好多類。
羅列一下
UIView+WebCache
UIView+WebCacheOperation
UIImageView+WebCache
UIImage+MultiFormat
UIImage+GIF
SDWebImageManager
SDWebImageOperation 是個協(xié)議
SDWebImageDownloaderOperation
SDWebImageDownloader
SDWebImageDecoder
SDImageCacheConfig
SDImageCache
哇撒,幾乎把所有的類都給包含進去了。
1.SDImageCache
我們先從數(shù)據(jù)緩存看起。
看一個類,先看數(shù)據(jù)結(jié)構(gòu)(我認為的哈)
這就是數(shù)據(jù)結(jié)構(gòu),比較簡單。public property 三個,private 四個,還有個成員變量
了解了結(jié)構(gòu),下面就看看初始化方法。
1.+ (nonnull instancetype)sharedImageCache;
2.- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
3.- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;
初始化方法有三個
第一個初始化
+ (nonnull instancetype)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
是個單例
- (instancetype)init {
return [self initWithNamespace:@"default"];
}
默認初始化調(diào)用 - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
最后都調(diào)用到了- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory 方法進行真正的初始化
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
_config = [[SDImageCacheConfig alloc] init];
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
});
#if SD_UIKIT
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {? ? NSArray*paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
初始化都干了啥事情呢
初始化 _ioQueue _config? _memCache (這里是AutoPurgeCache)_diskCachePath 和 _fileManager? 還有些通知。? _ioQueue 是串行隊列
接著往下看其他的不是初始化的方法。
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
這個方法實現(xiàn)很簡單,就是獲取緩存默認路徑
- (void)addReadOnlyCachePath:(nonnull NSString *)path?
這個函數(shù) 就是初始話_customPaths 給self.customPaths 增加一個路徑值。(初始化的時候是沒有初始化_customPaths成員變量的)
1- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
2- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
3.- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
這三個函數(shù)最終都是調(diào)用到了最后這個函數(shù)里面?
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
data = [image sd_imageDataAsFormat:imageFormatFromData];
}
[self storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
看這個函數(shù)實現(xiàn),其實很簡單,要是配置緩存到內(nèi)存就緩存到內(nèi)存,要是要寫入disk中,切換到self.ioQueue 異步寫入 到磁盤。寫入成功執(zhí)行回調(diào)block 要是參數(shù)error 直接調(diào)用block
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;
這個函數(shù)是最終寫入磁盤的函數(shù),不過這個函數(shù)要求在 self.ioQueue 中操作,同步寫入。
看到這里我們知道,SDWebImage 寫入到磁盤的數(shù)據(jù)都是在self.ioQueue 中,單個隊列管理。
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
查詢操作
查詢也需要切換到self.ioQueue 隊列中執(zhí)行。異步查詢,好到回到主線程回調(diào)
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
這個函數(shù)其實就是先去內(nèi)存中找數(shù)據(jù),要是沒找到,那就生成一個NSOperation 并且返回,異步去self.ioQueue 隊列去查詢圖片,要是找到了對圖片處理緩存到內(nèi)存中,在self.ioQueue隊列回調(diào)完成block。(這里為什么要返回一個NSOpertion呢?估計是下載和在磁盤上查找同時進行)?
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key
簡單從緩存中查找key數(shù)據(jù)
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key?
從磁盤是找找key ,找到如果配置到緩存,就緩存到內(nèi)存中
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;
這個從緩存中找,先沖內(nèi)存緩存找,找不到再到磁盤找
1- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion?
2- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;
折兩個函數(shù)一起看,都是緩存清除
要是配置緩存在內(nèi)存,就從內(nèi)存中移除圖片,接著異步切換到self.ioQueue 隊列中,對disk的數(shù)據(jù)進行刪除。
- (void)clearMemory;
清理記憶內(nèi)存的緩存圖片
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion
異步切換到 self.ioQueue 隊列中,對所有圖片進行刪除操作 刪除成功執(zhí)行回調(diào)block
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
這個函數(shù) 也是異步切換到self.ioQueue 隊列中執(zhí)行 刪除過期的文件 ?并且檢查緩存的圖片是否超過我們設(shè)定的硬盤圖片大小,要是超過就按照時間排序,刪除最老時候的圖片,直到緩存在硬盤的圖片大小比我們設(shè)定的小。我對這個函數(shù)比較感興趣,搜索了下調(diào)用 。該函數(shù)在- (void)backgroundDeleteOldFiles 調(diào)用,而- (void)backgroundDeleteOldFiles 調(diào)用的時機是[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil]; 當應(yīng)用進入后臺的時候就會清理過期的圖片,清理硬盤圖片到我們需要的大小為止。
這里可以修改自己想要的磁盤圖片管理。
- (NSUInteger)getSize;
這個是同步操作切換到self.ioQueue 獲取磁盤圖片大小。
- (NSUInteger)getDiskCount;
同步操作切換到self.ioQueue 獲取磁盤圖片數(shù)量
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;
這個是異步獲取圖片的大小
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path?
這個就是生成一個緩存路徑 ?
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key?
默認緩存路徑
這里就把SDImageCache 緩存給分析完畢
總結(jié)下
1:SDImageCache 管理者memoryCache 和 磁盤 上的圖片
2:memoryCache 讀取或者寫入都是在當前線程執(zhí)行
3:diskCache 都是在self.ioQueue隊列執(zhí)行
4:self.ioQueue 是串行隊列,一個一個任務(wù)執(zhí)行的。
5:清理緩存是在進入后臺的時候進行的。
這里面涉及到兩個類我們也瞅瞅AutoPurgeCache? SDImageCacheConfig
2.SDImageCacheConfig
這個類全是屬性。方法沒有
@property (assign, nonatomic) BOOL shouldDecompressImages;
默認圖片壓縮
@property (assign, nonatomic) BOOL shouldDisableiCloud;
是否保存iCould
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
是否緩存到記憶內(nèi)存
@property (assign, nonatomic) NSInteger maxCacheAge;
最大的緩存日期,默認七天
@property (assign, nonatomic) NSUInteger maxCacheSize;
最大緩存大小。默認是0 。0 代表不清理緩存。
3.AutoPurgeCache
AutoPurgeCache 繼承NSCache?
這個類就是NSCache 的功能,不過只是給這個類增加了一個接受內(nèi)存警告的通知罷了。
4.SDWebImageDownloaderOperation
接下來我們看看網(wǎng)絡(luò)請求部分
SDWebImageDownloaderOperation 繼承NSOperation 是NSOperation 的子類。
繼承NSOperation 的子類簡單實用方式
官方文檔:
For non-concurrent operations, you typically override only one method:
對于不是concurrent操作,你就覆蓋Main 函數(shù)就行了。
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
而對于concurrent操作,那么你需要覆蓋這四個方法。
那什么是concurrent 操作呢?我認為哈,請高手指正對不對,這里指的是同步操作或者異步操作。同步操作比如是同步讀取數(shù)據(jù)庫,寫入成功結(jié)束。而異步操作相當于異步寫入數(shù)據(jù)庫,等待回調(diào),寫入成功,接收回調(diào)操作結(jié)束。
那么測試試試吧。
測試代碼
定義一個子類NSOperationConcurrentSub
#importtypedef void(^CompleteSubBlock)(void);
#define __weakSelf? __weak typeof(self) weakSelf = self;
@interface NSOperationConcurrentSub : NSOperation
- (instancetype)initWithBlock:(CompleteSubBlock)block;
@end
#import "NSOperationConcurrentSub.h"
@interface NSOperationConcurrentSub()
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (copy,nonatomic) CompleteSubBlock block;
@end
@implementation NSOperationConcurrentSub
@synthesize executing = _executing;
@synthesize finished = _finished;
- (instancetype)initWithBlock:(CompleteSubBlock)block
{
self = [super init];
if (self) {
self.block = block;
}
return self;
}
-(void)start{
if (self.isCancelled) {
self.finished = YES;
return;
}
__weakSelf
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue
, ^{
sleep(1);
if (weakSelf==nil) {
weakSelf.finished = YES;
return ;
}
weakSelf.block();
weakSelf.finished = YES;
});
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isConcurrent {
return YES;
}
-(BOOL)isAsynchronous{
return YES;
}
@end
定義NSOperationNoConcurrentSub?
#import#import "NSOperationConcurrentSub.h"
@interface NSOperationNoConcurrentSub : NSOperation
- (instancetype)initWithBlock:(CompleteSubBlock)block;
@end
#import "NSOperationNoConcurrentSub.h"
@interface NSOperationNoConcurrentSub()
@property (copy,nonatomic) CompleteSubBlock block;
@end
@implementation NSOperationNoConcurrentSub
- (instancetype)initWithBlock:(CompleteSubBlock)block
{
self = [super init];
if (self) {
self.block = block;
}
return self;
}
-(void)main{
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__weakSelf
dispatch_async(aQueue
, ^{
sleep(1);
if (weakSelf==nil) {
return ;
}
weakSelf.block();
});
}
測試代碼viewController
#import "ViewController.h"
#import "NSOperationConcurrentSub.h"
#import "NSOperationNoConcurrentSub.h"
@interface ViewController ()
@property (nonatomic ,strong)NSOperationQueue * queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
self.queue = queue;
NSOperationConcurrentSub? *sub = [[NSOperationConcurrentSub alloc]initWithBlock:^{
NSLog(@"NSOperationConcurrentSub");
}];
NSOperationNoConcurrentSub * nosub = [[NSOperationNoConcurrentSub alloc]initWithBlock:^{
NSLog(@"NSOperationNoConcurrentSub");
}];
[queue addOperation:sub];
[queue addOperation:nosub];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
輸出結(jié)果是
2017-10-30 11:57:46.100736+0800 NSOpertionTest[47985:822761] NSOperationConcurrentSub
沒有執(zhí)行NSOperationNoConcurrentSub 中的block。說明no-concurrentSub 只能執(zhí)行同步操作,操作中不能有分支。
而,NSOperationConcurrentSub 中的block執(zhí)行了。說明,可以分叉執(zhí)行。
而SDWebImage中的SDWebImageDownloaderOperation 因為里面有異步操作請求圖片的過程,所以用的是concurrent方式。
網(wǎng)絡(luò)請求是用NSURLSessionTask 任務(wù)形式的,網(wǎng)上講這個怎么用的方式一大把,這里就不啰嗦了。
我們下面看這個類的結(jié)構(gòu)
屬性不少。接著我們看方法
先看初始化操作
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER;
- (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;
}
這里生成一個對象,一個初始化了九個成員變量。沒啥可看的。
- (nonnull instancetype)init {
return [self initWithRequest:nil inSession:nil options:0];
}
這個是調(diào)用默認初始化。request 和 session 都是nil 。意思是沒請求。不過這干嘛不來個log打印呢。
第二個public 方法是
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
這個就是給進度條加入到數(shù)組中。
第三個public 方法
- (BOOL)cancel:(nullable id)token
- (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;
}
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.dataTask) {
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
// 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)reset {
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
});
self.dataTask = nil;
self.imageData = nil;
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
取消做的事情比較多,
1。第一步是移除callbackBlocks 數(shù)組 相關(guān)數(shù)據(jù)
2.要是callbackBlocks 數(shù)組中的數(shù)量為0,調(diào)用cancel函數(shù)
3.cancel 函數(shù)加鎖接著掉用cancelInternal 函數(shù)
4.檢測operation 是否結(jié)束。沒結(jié)束super調(diào)用 cancel
5.要是有dataTask 那么dataTask 調(diào)用cancel,并且發(fā)送通知
6.復(fù)位
public方法說完了。
當NSOperation 加入到NSOperationQueue 中,上面說到concurrent 會調(diào)用到start 方法。
所以我們這里有個默認的public方法。那就是 start?
分析start方法
分段分析
第一段
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
就是要說operation 取消了,就reset下
第二段
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;
}
}];
}
要是配置后臺運行下載,那么等后臺下載結(jié)束后結(jié)束后臺下載進程。
第三段
NSURLSession *session = self.unownedSession;
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;
}
要說外界沒有傳入session 。那么我們就生成ownedSession ,別的就用unownedSession?
第四段
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
用session生成task任務(wù)。 并且讓operation 進入executing 狀態(tài)
第五段
[self.dataTask resume];
啟動任務(wù)下載
第六段
if (self.dataTask) {
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
要說有任務(wù)就開始啟動進度條。沒有就回調(diào)error
第七段
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;
}
要是self.backgroundTaskId 不是 UIBackgroundTaskInvalid 那么就結(jié)束后臺執(zhí)行
這里有點疑問,假設(shè)開啟了backgoundTask 任務(wù),怎么在start 結(jié)束后直接將backgroundTask任務(wù)給關(guān)閉了。那不就是沒有后臺任務(wù)了。難道只是為了保證在任何沒有殺死app的其他任何情況下都啟動任務(wù)?
好了這就是網(wǎng)絡(luò)請求發(fā)出去的過程。網(wǎng)絡(luò)請求發(fā)出去肯定就要接收呀。
數(shù)據(jù)接收在 NSURLSessionDataDelegate, NSURLSessionTaskDelegate ?
SDwebImage 中
NSURLSessionTaskDelegate代理實現(xiàn)了兩個?
1.- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler?
2.- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error?
NSURLSessionDataDelegate 代理實現(xiàn)了三個
1.- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
2.- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
3.- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
這兩個的代理還有很多,不過于研究。
這里這些代理的執(zhí)行先后順序是
1.- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
2.- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
3.- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
4.- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
5.- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
我們先看第一個執(zhí)行的代理方法
- (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]) {
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);
}
}
這里是接受到網(wǎng)絡(luò)連接后的第一個回調(diào)函數(shù)。好多新的類,媽呀。看著頭暈。這都是什么鬼。沒辦法只能一個一個看下去了。NSURLSession 和 NSURLSessionTask 講解的很多,這里不做說明,看NSURLAuthenticationChallenge ?這是啥呢?
Most apps do not create authentication challenges themselves. However, you might need to create authentication challenge objects when adding support for custom networking protocols, as part of your customNSURLProtocolsubclasses.
這句話就是一般不需要鑒定,但是如果自己實現(xiàn)的協(xié)議,那么可能就有用了。
Instead, your app receives authentication challenges in variousNSURLSession,NSURLConnection, andNSURLDownloaddelegate methods, such asURLSession:task:didReceiveChallenge:completionHandler:. These objects provide the information you’ll need when deciding how to handle a server’s request for authentication. At the core of that authentication challenge is aprotection spacethat defines the type of authentication being requested, the host and port number, the networking protocol, and (where applicable) the authentication realm (a group of related URLs on the same server that share a single set of credentials).
鑒定的核心有個space 這個包含請求的host port 網(wǎng)絡(luò)協(xié)議等等。
Your app responds to authentication challenges by providing anNSURLCredentialobject. The details depend on the API you are using and on the type of challenge.
我們響應(yīng)應(yīng)該通過NSURLCredential?對象respond。具體依賴api
At a high level, if you’re providing the user’s credentials to a server or proxy, theproposedCredentialmethod provides a credential that matches the criteria specified in the protection space, retrieved from theNSURLCredentialStorageclass handling the request (assuming such a credential exists).
If thepreviousFailureCountmethod returns 0 and the proposed credential exists, the proposed credential has not yet been tried, which means you should try it. If it returns a nonzero result, then the server has rejected the proposed credential, and you should use that credential to populate a password or certificate chooser dialog, then provide a new credential. You can create password-based credentials by calling thecredentialWithUser:password:persistence:method or create certificate-based credentials with thecredentialWithIdentity:certificates:persistence:.
If the authentication’s protection space uses theNSURLAuthenticationMethodServerTrustauthentication method, the request is asking you to verify the server’s authenticity. In this case, theproposedCredentialmethod provides a credential based on the certificates that the server provided as part of its initial TLS handshake. Most apps should request default handling for authentication challenges based on a server trust protection space, but if you need to override the default TLS validation behavior, you can do so as described inOverriding TLS Chain Validation Correctly.
previousFailureCount 為0 代表credential存在。不是0 ,服務(wù)器已經(jīng)拒絕了。那就只能新建credential。
For more information about how URL sessions handle the different types of authentication challenges, seeNSURLSessionandURL Session Programming Guide. ? ?
官方文檔原話,就是上面這樣的。在哪呢? cmd + shift +0 ,搜索 NSURLAuthenticationChallenge。?
上面只是大概翻譯下。明白就好。返回來看代碼。?講解證書信任
這個方法是使用https 才會調(diào)用的,調(diào)用http是不會調(diào)用到這里的,這個只是證書驗證過程。默認使用NSURLSessionAuthChallengePerformDefaultHandling 默認配置,要是配置可以允許無效證書,那么就創(chuàng)建一個NSURLCredential 并且信任他。
要是不信任 的證書,判斷previousFailureCount =0 ,0代表服務(wù)器信任了。檢查有沒有self.credential ,沒有就取消這個操作。
2.- (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;
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
} 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.
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
[self done];
}
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
第二個回調(diào)是接受響應(yīng)體。
其實是分三部分處理的。code = 304的, code<=400 一下的,再就是剩下code其他的
。這里不對http請求code 碼做單獨解讀,可以自行百度。
為啥code 304 單獨處理呢?
304(未修改)自從上次請求后,請求的網(wǎng)頁未修改過。服務(wù)器返回此響應(yīng)時,不會返回網(wǎng)頁內(nèi)容。
如果網(wǎng)頁自請求者上次請求后再也沒有更改過,您應(yīng)將服務(wù)器配置為返回此響應(yīng)(稱為 If-Modified-Since HTTP 標頭)。服務(wù)器可以告訴 Googlebot 自從上次抓取后網(wǎng)頁沒有變更,進而節(jié)省帶寬和開銷。
先看code 400以下,并且不是304的部分
首先從NSURLResponse讀取接收數(shù)據(jù)長度,保存到_expectedSize 變量中,并且開始進度條從0開始調(diào)用。這里初始化_imageData 變量,保存response到變量_response中,再發(fā)一個通知。結(jié)束。
code =304 的直接 調(diào)用cancelInternal
其他的直接取消下載任務(wù)。
3- (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);
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);
}
}
這個函數(shù)算是真正的下載了。要是有數(shù)據(jù),這個函數(shù)至少要調(diào)用一次,把所有數(shù)據(jù)拼接到一起就是本次下載的圖片數(shù)據(jù)。
那我們分析這個這個函數(shù)。
第一步,給self.imageData 追加數(shù)據(jù)
第二步,如果數(shù)據(jù)長度大于0 并且配置了進度條 。執(zhí)行下面操作。
? ? ? ? ? ? ? ?1. 獲取當前數(shù)據(jù)的長度。
? ? ? ? ? ? ? 2 將當前數(shù)據(jù)轉(zhuǎn)換成CGImageSourceRef。
? ? ? ? ? ? ?3 檢測變量width 和 height 都是0 的話,那么就讀取圖片的長和寬給變量賦值。也獲取下圖片的方向。
? ? ? ? ? ? 4 獲取圖片的長款還有當前數(shù)據(jù)長度小于接受數(shù)據(jù)總長度。執(zhí)行下面操作。將CGImageSourceRef 轉(zhuǎn)換成image,如果生成了CGimage,將圖片處理成bitmap,接著生成UIimage。對圖片進行一系列處理,然后返回。
第三步 :就是回調(diào)進度條。
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
這個要是配置忽略本地緩存,就忽略
第五個方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
}
});
}
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];
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) {
if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
#if SD_UIKIT || SD_WATCH
image = [UIImage decodedAndScaledDownImageWithImage:image];
[self.imageData setData:UIImagePNGRepresentation(image)];
#endif
} else {
image = [UIImage decodedImageWithImage:image];
}
}
}
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];
}
這個類我認為哈,關(guān)鍵點barrierQueue ,這個是干嘛用的。不用barrierQueue 行不行,搜索下這個類,與barrierQueue 關(guān)聯(lián)在一起的是callbackBlocks 這個數(shù)組,所有的callbackBlocks 數(shù)組操作都是切換到barrierQueue 中的,有時候用dispatch_barrier_async 有時候用 dispatch_sync 還有時候用 dispatch_barrier_sync。
雖然講解這三個 GCD 函數(shù)的demo博客很多,但是具體到這個線程中是什么狀態(tài)呢?
我們需要做個demo 試驗下。
測試代碼
#import "NSOperationConcurrentSub.h"
@interface NSOperationConcurrentSub()
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (copy,nonatomic) CompleteSubBlock block;
@end
@implementation NSOperationConcurrentSub
@synthesize executing = _executing;
@synthesize finished = _finished;
- (instancetype)initWithBlock:(CompleteSubBlock)block
{
self = [super init];
if (self) {
self.block = block;
}
return self;
}
-(void)start{
if (self.isCancelled) {
self.finished = YES;
return;
}
//? ? [self ddd];
[self test];
}
-(void)test{
NSLog(@" queue begin");
dispatch_queue_t aQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t newQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(newQueue, ^{
sleep(8);
NSLog(@"new_dispatch_async");
});
dispatch_async(aQueue, ^{
sleep(3);
NSLog(@"dispatch_async");
});
dispatch_barrier_async(aQueue, ^{
sleep(2);
NSLog(@"dispatch_barrier_async");
});
dispatch_async(aQueue, ^{
sleep(1);
NSLog(@"dispatch_async");
});
dispatch_async(newQueue, ^{
NSLog(@"new_dispatch_async");
});
NSLog(@" queue end");
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isConcurrent {
return YES;
}
-(BOOL)isAsynchronous{
return YES;
}
@end
#importtypedef void(^CompleteSubBlock)(void);
#define __weakSelf? __weak typeof(self) weakSelf = self;
@interface NSOperationConcurrentSub : NSOperation
- (instancetype)initWithBlock:(CompleteSubBlock)block;
@end
#import "ViewController.h"
#import "NSOperationConcurrentSub.h"
@interface ViewController ()
@property (nonatomic ,strong)NSOperationQueue * queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
self.queue = queue;
NSOperationConcurrentSub? *sub = [[NSOperationConcurrentSub alloc]initWithBlock:^{
NSLog(@"NSOperationConcurrentSub");
}];
NSLog(@"begin");
[queue addOperation:sub];
sleep(1);
NSLog(@"end");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
測試結(jié)果
2017-10-31 18:23:07.439753+0800 NSOpertionTest[38787:452080] begin
2017-10-31 18:23:07.440018+0800 NSOpertionTest[38787:452132]? queue begin
2017-10-31 18:23:07.440162+0800 NSOpertionTest[38787:452132]? queue end
2017-10-31 18:23:07.440198+0800 NSOpertionTest[38787:452124] new_dispatch_async
2017-10-31 18:23:08.440141+0800 NSOpertionTest[38787:452080] end
2017-10-31 18:23:10.444277+0800 NSOpertionTest[38787:452126] dispatch_async
2017-10-31 18:23:12.447935+0800 NSOpertionTest[38787:452126] dispatch_barrier_async
2017-10-31 18:23:13.451442+0800 NSOpertionTest[38787:452126] dispatch_async
2017-10-31 18:23:15.441778+0800 NSOpertionTest[38787:452125] new_dispatch_async
可以看出dispatch_barrier_async ?管理的是自己的隊列,不會影響其他的隊列和當前線程。
將dispatch_barrier_async 改成dispatch_barrier_sync?
結(jié)果是
2017-10-31 18:20:47.819449+0800 NSOpertionTest[38756:450698] begin
2017-10-31 18:20:47.819702+0800 NSOpertionTest[38756:450789]? queue begin
2017-10-31 18:20:48.819873+0800 NSOpertionTest[38756:450698] end
2017-10-31 18:20:50.823113+0800 NSOpertionTest[38756:450787] dispatch_async
2017-10-31 18:20:52.824665+0800 NSOpertionTest[38756:450789] dispatch_barrier_async
2017-10-31 18:20:52.824860+0800 NSOpertionTest[38756:450789]? queue end
2017-10-31 18:20:52.824875+0800 NSOpertionTest[38756:450788] new_dispatch_async
2017-10-31 18:20:53.827384+0800 NSOpertionTest[38756:450787] dispatch_async
2017-10-31 18:20:55.820566+0800 NSOpertionTest[38756:450786] new_dispatch_async
dispatch_barrier_sync 影響的是當前線程和傳入的隊列,對其他隊列別影響。
那么返回sdwebimage 看源碼這個隊列啥意思。一共有四處使用這個隊列
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
- (nullable NSArray*)callbacksForKey:(NSString *)key {? ? __block NSMutableArray*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
}
- (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;
}
- (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;
}
}
而這四處操作的都是callbackBlocks 這個變量
接下來分析分析這四處地方的使用
在- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock方法中
使用的是 下列的方式
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
給數(shù)組增加一個對象。這說明數(shù)組中可以隨時增加對象。
在- (nullable NSArray*)callbacksForKey:(NSString *)key方法中
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]];
});
獲取數(shù)據(jù) 是用的是dispatch_sync ?,相當于一條線執(zhí)行,執(zhí)行完我在回到當前線程執(zhí)行,獲取數(shù)據(jù)。
第三個方法- (BOOL)cancel:(nullable id)token
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
要是self.barrierQueue 隊列中有任務(wù)那么就等待所有任務(wù)結(jié)束,結(jié)束后再執(zhí)行我。并且當前線程也暫停。
而reset方法呢
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
});
刪除所有對象
在操作self.callbackBlocks 數(shù)組的時候,只有在增加數(shù)據(jù)或者刪除所有數(shù)據(jù)的時候調(diào)用dispatch_barrier_async? 而在獲取數(shù)據(jù)的時候用的dispatch_sync 而在刪除某個數(shù)據(jù)的時候用的是 dispatch_barrier_sync 。
為啥這樣呢?我認為是dispatch_barrier_sync 是cancel 方法,要取消當前的操作,所以當前線程就沒有必要執(zhí)行其他操作了。執(zhí)行到這里行了。取消線程,并且依賴dispatch_barrier_sync block中執(zhí)行過程中的數(shù)據(jù)。
而增加或者刪除數(shù)組元素操作,必須要用dispatch_barrier_async 而不是dispatch_async ,保證數(shù)據(jù)的加入和刪除是有順序的。