寫在開頭:
- 大概回憶下,之前我們講了
AFNetworking
整個網(wǎng)絡(luò)請求的流程臂拓,包括request
的拼接商乎,session
代理的轉(zhuǎn)發(fā)距境,response
的解析申尼。以及對一些bug
的適配,如果你還沒有看過垫桂,可以點這里:
AFNetworking到底做了什么?
AFNetworking到底做了什么(二)? - 除此之外我們還單獨的開了一篇講了AF對
https
的處理:
AFNetworking之于https認證 - 本文將涉及部分AF對UIKit的擴展與圖片下載相關(guān)緩存的實現(xiàn)师幕,文章內(nèi)容相對獨立,如果沒看過前文诬滩,也不影響閱讀霹粥。
回到正文:
我們來看看AF對UIkit
的擴展:
一共如上這個多類,下面我們開始著重講其中兩個UIKit的擴展:
- 一個是我們網(wǎng)絡(luò)請求時狀態(tài)欄的小菊花疼鸟。
- 一個是我們幾乎都用到過請求網(wǎng)絡(luò)圖片的如下一行方法:
- (void)setImageWithURL:(NSURL *)url ;
我們開始吧:
1.AFNetworkActivityIndicatorManager
這個類的作用相當簡單后控,就是當網(wǎng)絡(luò)請求的時候,狀態(tài)欄上的小菊花就會開始轉(zhuǎn):
需要的代碼也很簡單空镜,只需在你需要它的位置中(比如AppDelegate)導入類浩淘,并加一行代碼即可:
#import "AFNetworkActivityIndicatorManager.h"
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
接下來我們來講講這個類的實現(xiàn):
這個類的實現(xiàn)也非常簡單,還記得我們之前講的AF對
NSURLSessionTask
中做了一個Method Swizzling嗎吴攒?大意是把它的resume
和suspend
方法做了一個替換张抄,在原有實現(xiàn)的基礎(chǔ)上添加了一個通知的發(fā)送。這個類就是基于這兩個通知和task完成的通知來實現(xiàn)的洼怔。
首先我們來看看它的初始化方法:
+ (instancetype)sharedManager {
static AFNetworkActivityIndicatorManager *_sharedManager = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedManager = [[self alloc] init];
});
return _sharedManager;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
//設(shè)置狀態(tài)為沒有request活躍
self.currentState = AFNetworkActivityManagerStateNotActive;
//開始下載通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
//掛起通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
//完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
//開始延遲
self.activationDelay = kDefaultAFNetworkActivityManagerActivationDelay;
//結(jié)束延遲
self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay;
return self;
}
- 初始化如上署惯,設(shè)置了一個state,這個state是一個枚舉:
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
//沒有請求
AFNetworkActivityManagerStateNotActive,
//請求延遲開始
AFNetworkActivityManagerStateDelayingStart,
//請求進行中
AFNetworkActivityManagerStateActive,
//請求延遲結(jié)束
AFNetworkActivityManagerStateDelayingEnd
};
這個state一共如上4種狀態(tài)镣隶,其中兩種應(yīng)該很好理解泽台,而延遲開始和延遲結(jié)束怎么理解呢?
原來這是AF對請求菊花顯示做的一個優(yōu)化處理矾缓,試問如果一個請求時間很短,那么菊花很可能閃一下就結(jié)束了稻爬。如果很多請求過來嗜闻,那么菊花會不停的閃啊閃,這顯然并不是我們想要的效果桅锄。
所以多了這兩個參數(shù):
1)在一個請求開始的時候琉雳,我延遲一會在去轉(zhuǎn)菊花样眠,如果在這延遲時間內(nèi),請求結(jié)束了翠肘,那么我就不需要去轉(zhuǎn)菊花了檐束。
2)但是一旦轉(zhuǎn)菊花開始,哪怕很短請求就結(jié)束了束倍,我們還是會去轉(zhuǎn)一個時間再去結(jié)束被丧,這時間就是延遲結(jié)束的時間。緊接著我們監(jiān)聽了三個通知绪妹,用來監(jiān)聽當前正在進行的網(wǎng)絡(luò)請求的狀態(tài)甥桂。
然后設(shè)置了我們前面提到的這個轉(zhuǎn)菊花延遲開始和延遲結(jié)束的時間,這兩個默認值如下:
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
接著我們來看看三個通知觸發(fā)調(diào)用的方法:
//請求開始
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
//增加請求活躍數(shù)
[self incrementActivityCount];
}
}
//請求結(jié)束
- (void)networkRequestDidFinish:(NSNotification *)notification {
//AFNetworkRequestFromNotification(notification)返回這個通知的request,用來判斷request是否是有效的
if ([AFNetworkRequestFromNotification(notification) URL]) {
//減少請求活躍數(shù)
[self decrementActivityCount];
}
}
方法很簡單邮旷,就是開始的時候增加了請求活躍數(shù)黄选,結(jié)束則減少。調(diào)用了如下兩個方法進行加減:
//增加請求活躍數(shù)
- (void)incrementActivityCount {
//活躍的網(wǎng)絡(luò)數(shù)+1婶肩,并手動發(fā)送KVO
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
//主線程去做
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
//減少請求活躍數(shù)
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
_activityCount = MAX(_activityCount - 1, 0);
#pragma clang diagnostic pop
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
方法做了什么應(yīng)該很容易看明白办陷,這里需要注意的是,task的幾個狀態(tài)的通知律歼,是會在多線程的環(huán)境下發(fā)送過來的民镜。所以這里對活躍數(shù)的加減,都用了@synchronized
這種方式的鎖苗膝,進行了線程保護殃恒。然后回到主線程調(diào)用了updateCurrentStateForNetworkActivityChange
我們接著來看看這個方法:
- (void)updateCurrentStateForNetworkActivityChange {
//如果是允許小菊花
if (self.enabled) {
switch (self.currentState) {
//不活躍
case AFNetworkActivityManagerStateNotActive:
//判斷活躍數(shù),大于0為YES
if (self.isNetworkActivityOccurring) {
//設(shè)置狀態(tài)為延遲開始
[self setCurrentState:AFNetworkActivityManagerStateDelayingStart];
}
break;
case AFNetworkActivityManagerStateDelayingStart:
//No op. Let the delay timer finish out.
break;
case AFNetworkActivityManagerStateActive:
if (!self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingEnd];
}
break;
case AFNetworkActivityManagerStateDelayingEnd:
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
}
break;
}
}
}
- 這個方法先是判斷了我們一開始設(shè)置是否需要菊花的
self.enabled
辱揭,如果需要离唐,才執(zhí)行。 - 這里主要是根據(jù)當前的狀態(tài)问窃,來判斷下一個狀態(tài)應(yīng)該是什么亥鬓。其中有這么一個屬性
self.isNetworkActivityOccurring
:
//判斷是否活躍
- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}
那么這個方法應(yīng)該不難理解了。
這個類復(fù)寫了currentState的set方法域庇,每當我們改變這個state嵌戈,就會觸發(fā)set方法,而怎么該轉(zhuǎn)菊花也在該方法中:
//設(shè)置當前小菊花狀態(tài)
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
@synchronized(self) {
if (_currentState != currentState) {
//KVO
[self willChangeValueForKey:@"currentState"];
_currentState = currentState;
switch (currentState) {
//如果為不活躍
case AFNetworkActivityManagerStateNotActive:
//取消兩個延遲用的timer
[self cancelActivationDelayTimer];
[self cancelCompletionDelayTimer];
//設(shè)置小菊花不可見
[self setNetworkActivityIndicatorVisible:NO];
break;
case AFNetworkActivityManagerStateDelayingStart:
//開啟一個定時器延遲去轉(zhuǎn)菊花
[self startActivationDelayTimer];
break;
//如果是活躍狀態(tài)
case AFNetworkActivityManagerStateActive:
//取消延遲完成的timer
[self cancelCompletionDelayTimer];
//開始轉(zhuǎn)菊花
[self setNetworkActivityIndicatorVisible:YES];
break;
//延遲完成狀態(tài)
case AFNetworkActivityManagerStateDelayingEnd:
//開啟延遲完成timer
[self startCompletionDelayTimer];
break;
}
}
[self didChangeValueForKey:@"currentState"];
}
}
這個set方法就是這個類最核心的方法了听皿。它的作用如下:
- 這里根據(jù)當前狀態(tài)熟呛,是否需要開始執(zhí)行一個延遲開始或者延遲完成,又或者是否需要取消這兩個延遲尉姨。
- 還判斷了庵朝,是否需要去轉(zhuǎn)狀態(tài)欄的菊花,調(diào)用了
setNetworkActivityIndicatorVisible:
方法:
- (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible {
if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) {
[self willChangeValueForKey:@"networkActivityIndicatorVisible"];
@synchronized(self) {
_networkActivityIndicatorVisible = networkActivityIndicatorVisible;
}
[self didChangeValueForKey:@"networkActivityIndicatorVisible"];
//支持自定義的Block,去自己控制小菊花
if (self.networkActivityActionBlock) {
self.networkActivityActionBlock(networkActivityIndicatorVisible);
} else {
//否則默認AF根據(jù)該Bool九府,去控制狀態(tài)欄小菊花是否顯示
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
}
}
}
- 這個方法就是用來控制菊花是否轉(zhuǎn)椎瘟。并且支持一個自定義的Block,我們可以自己去拿到這個菊花是否應(yīng)該轉(zhuǎn)的狀態(tài)值,去做一些自定義的處理侄旬。
- 如果我們沒有實現(xiàn)這個Block肺蔚,則調(diào)用:
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
去轉(zhuǎn)菊花。
回到state的set方法中儡羔,我們除了控制菊花去轉(zhuǎn)宣羊,還調(diào)用了以下4個方法:
//開始任務(wù)到結(jié)束的時間,默認為1秒笔链,如果1秒就結(jié)束段只,那么不轉(zhuǎn)菊花,延遲去開始轉(zhuǎn)
- (void)startActivationDelayTimer {
//只執(zhí)行一次
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
//添加到主線程runloop去觸發(fā)
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
//完成任務(wù)到下一個任務(wù)開始鉴扫,默認為0.17秒赞枕,如果0.17秒就開始下一個,那么不停 延遲去結(jié)束菊花轉(zhuǎn)
- (void)startCompletionDelayTimer {
//先取消之前的
[self.completionDelayTimer invalidate];
//延遲執(zhí)行讓菊花不在轉(zhuǎn)
self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)cancelActivationDelayTimer {
[self.activationDelayTimer invalidate];
}
- (void)cancelCompletionDelayTimer {
[self.completionDelayTimer invalidate];
}
這4個方法分別是開始延遲執(zhí)行一個方法坪创,和結(jié)束的時候延遲執(zhí)行一個方法炕婶,和對應(yīng)這兩個方法的取消。其作用莱预,注釋應(yīng)該很容易理解柠掂。
我們繼續(xù)往下看,這兩個延遲調(diào)用的到底是什么:
- (void)activationDelayTimerFired {
//活躍狀態(tài)依沮,即活躍數(shù)大于1才轉(zhuǎn)
if (self.networkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
} else {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
- (void)completionDelayTimerFired {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
一個開始涯贞,一個完成調(diào)用,都設(shè)置了不同的currentState的值危喉,又回到之前state
的set
方法中了宋渔。
至此這個AFNetworkActivityIndicatorManager
類就講完了,代碼還是相當簡單明了的辜限。
2.UIImageView+AFNetworking
接下來我們來講一個我們經(jīng)常用的方法皇拣,這個方法的實現(xiàn)類是:UIImageView+AFNetworking.h
。
這是個類目薄嫡,并且給UIImageView擴展了4個方法:
- (void)setImageWithURL:(NSURL *)url;
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
- (void)cancelImageDownloadTask;
- 前兩個想必不用我說了氧急,沒有誰沒用過吧...就是給一個UIImageView去異步的請求一張圖片,并且可以設(shè)置一張占位圖毫深。
- 第3個方法設(shè)置一張圖吩坝,并且可以拿到成功和失敗的回調(diào)。
- 第4個方法哑蔫,可以取消當前的圖片設(shè)置請求钾恢。
無論SDWebImage
,還是YYKit
,或者AF
手素,都實現(xiàn)了這么個類目。
AF關(guān)于這個類目UIImageView+AFNetworking
的實現(xiàn)瘩蚪,依賴于這么兩個類:AFImageDownloader
,AFAutoPurgingImageCache
稿黍。
當然AFImageDownloader
中疹瘦,關(guān)于圖片數(shù)據(jù)請求的部分,還是使用AFURLSessionManager
來實現(xiàn)的巡球。
接下來我們就來看看AFImageDownloader:
先看看初始化方法:
//該類為單例
+ (instancetype)defaultInstance {
static AFImageDownloader *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
return [self initWithSessionManager:sessionManager
downloadPrioritization:AFImageDownloadPrioritizationFIFO
maximumActiveDownloads:4
imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//TODO set the default HTTP headers
configuration.HTTPShouldSetCookies = YES;
configuration.HTTPShouldUsePipelining = NO;
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
//是否允許蜂窩網(wǎng)絡(luò)言沐,手機網(wǎng)
configuration.allowsCellularAccess = YES;
//默認超時
configuration.timeoutIntervalForRequest = 60.0;
//設(shè)置的圖片緩存對象
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
該類為單例,上述方法中酣栈,創(chuàng)建了一個sessionManager
,這個sessionManager
將用于我們之后的網(wǎng)絡(luò)請求险胰。從這里我們可以看到,這個類的網(wǎng)絡(luò)請求都是基于之前AF自己封裝的AFHTTPSessionManager
矿筝。
- 在這里初始化了一系列的對象起便,需要講一下的是
AFImageDownloadPrioritizationFIFO
,這個一個枚舉值:
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
//先進先出
AFImageDownloadPrioritizationFIFO,
//后進先出
AFImageDownloadPrioritizationLIFO
};
這個枚舉值代表著窖维,一堆圖片下載榆综,執(zhí)行任務(wù)的順序。
- 還有一個
AFAutoPurgingImageCache
的創(chuàng)建铸史,這個類是AF做圖片緩存用的鼻疮。這里我們暫時就這么理解它,講完當前類琳轿,我們再來補充它判沟。 - 除此之外,我們還看到一個cache:
configuration.URLCache = [AFImageDownloader defaultURLCache];
//設(shè)置一個系統(tǒng)緩存崭篡,內(nèi)存緩存為20M挪哄,磁盤緩存為150M,
//這個是系統(tǒng)級別維護的緩存媚送。
+ (NSURLCache *)defaultURLCache {
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:150 * 1024 * 1024
diskPath:@"com.alamofire.imagedownloader"];
}
大家看到這可能迷惑了中燥,怎么這么多cache,那AF做圖片緩存到底用哪個呢塘偎?答案是AF自己控制的圖片緩存用AFAutoPurgingImageCache
疗涉,而NSUrlRequest
的緩存由它自己內(nèi)部根據(jù)策略去控制,用的是NSURLCache
吟秩,不歸AF處理咱扣,只需在configuration中設(shè)置上即可。
- 那么看到這有些小伙伴又要問了涵防,為什么不直接用
NSURLCache
闹伪,還要自定義一個AFAutoPurgingImageCache
呢?原來是因為NSURLCache
的諸多限制,例如只支持get請求等等偏瓤。而且因為是系統(tǒng)維護的杀怠,我們自己的可控度不強,并且如果需要做一些自定義的緩存處理厅克,無法實現(xiàn)赔退。 - 更多關(guān)于
NSURLCache
的內(nèi)容,大家可以自行查閱证舟。
接著上面的方法調(diào)用到這個最終的初始化方法中:
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache {
if (self = [super init]) {
//持有
self.sessionManager = sessionManager;
//定義下載任務(wù)的順序硕旗,默認FIFO,先進先出-隊列模式女责,還有后進先出-棧模式
self.downloadPrioritizaton = downloadPrioritization;
//最大的下載數(shù)
self.maximumActiveDownloads = maximumActiveDownloads;
//自定義的cache
self.imageCache = imageCache;
//隊列中的任務(wù)漆枚,待執(zhí)行的
self.queuedMergedTasks = [[NSMutableArray alloc] init];
//合并的任務(wù),所有任務(wù)的字典
self.mergedTasks = [[NSMutableDictionary alloc] init];
//活躍的request數(shù)
self.activeRequestCount = 0;
//用UUID來拼接名字
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
//創(chuàng)建一個串行的queue
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
//創(chuàng)建并行queue
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
這邊初始化了一些屬性抵知,這些屬性跟著注釋看應(yīng)該很容易明白其作用墙基。主要需要注意的就是,這里創(chuàng)建了兩個queue:一個串行的請求queue辛藻,和一個并行的響應(yīng)queue碘橘。
- 這個串行queue,是用來做內(nèi)部生成task等等一系列業(yè)務(wù)邏輯的。它保證了我們在這些邏輯處理中的線程安全問題(迷惑的接著往下看)。
- 這個并行queue,被用來做網(wǎng)絡(luò)請求完成的數(shù)據(jù)回調(diào)笆制。
接下來我們來看看它的創(chuàng)建請求task的方法:
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success
failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure {
return [self downloadImageForURLRequest:request withReceiptID:[NSUUID UUID] success:success failure:failure];
}
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(nonnull NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
//還是類似之前的,同步串行去做下載的事 生成一個task,這些事情都是在當前線程中串行同步做的纺蛆,所以不用擔心線程安全問題。
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
//url字符串
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
//錯誤返回规揪,沒Url
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
//如果這個任務(wù)已經(jīng)存在桥氏,則添加成功失敗Block,然后直接返回,即一個url用一個request,可以響應(yīng)好幾個block
//從自己task字典中根據(jù)Url去取AFImageDownloaderMergedTask猛铅,里面有task id url等等信息
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
//里面包含成功和失敗Block和UUid
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
//添加handler
[existingMergedTask addResponseHandler:handler];
//給task賦值
task = existingMergedTask.task;
return;
}
//根據(jù)request的緩存策略字支,加載緩存
switch (request.cachePolicy) {
//這3種情況都會去加載緩存
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
//從cache中根據(jù)request拿數(shù)據(jù)
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
//走到這說明即沒有請求中的request,也沒有cache,開始請求
NSUUID *mergedTaskIdentifier = [NSUUID UUID];
//task
NSURLSessionDataTask *createdTask;
__weak __typeof__(self) weakSelf = self;
//用sessionManager的去請求,注意奸忽,只是創(chuàng)建task,還是掛起狀態(tài)
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
//在responseQueue中回調(diào)數(shù)據(jù),初始化為并行queue
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
//拿到當前的task
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
//如果之前的task數(shù)組中堕伪,有這個請求的任務(wù)task,則從數(shù)組中移除
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
//安全的移除栗菜,并返回當前被移除的AF task
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
//請求錯誤
if (error) {
//去遍歷task所有響應(yīng)的處理
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
//主線程欠雌,調(diào)用失敗的Block
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
});
}
}
} else {
//成功根據(jù)request,往cache里添加
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
//調(diào)用成功Block
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
});
}
}
}
}
//減少活躍的任務(wù)數(shù)
[strongSelf safelyDecrementActiveTaskCount];
[strongSelf safelyStartNextTaskIfNecessary];
});
}];
// 4) Store the response handler for use when the request completes
//創(chuàng)建handler
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
//創(chuàng)建task
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
//添加handler
[mergedTask addResponseHandler:handler];
//往當前任務(wù)字典里添加任務(wù)
self.mergedTasks[URLIdentifier] = mergedTask;
// 5) Either start the request or enqueue it depending on the current active request count
//如果小于,則開始任務(wù)下載resume
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
//拿到最終生成的task
task = mergedTask.task;
});
if (task) {
//創(chuàng)建一個AFImageDownloadReceipt并返回疙筹,里面就多一個receiptID富俄。
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
}
就這么一個非常非常長的方法禁炒,這個方法執(zhí)行的內(nèi)容都是在我們之前創(chuàng)建的串行queue中,同步的執(zhí)行的霍比,這是因為這個方法絕大多數(shù)的操作都是需要線程安全的幕袱。可以對著源碼和注釋來看桂塞,我們在這講下它做了什么:
- 首先做了一個url的判斷凹蜂,如果為空則返回失敗Block。
- 判斷這個需要請求的url阁危,是不是已經(jīng)被生成的task中,如果是的話汰瘫,則多添加一個回調(diào)處理就可以狂打。回調(diào)處理對象為
AFImageDownloaderResponseHandler
混弥。這個類非常簡單趴乡,總共就如下3個屬性:
@interface AFImageDownloaderResponseHandler : NSObject
@property (nonatomic, strong) NSUUID *uuid;
@property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
@end
@implementation AFImageDownloaderResponseHandler
//初始化回調(diào)對象
- (instancetype)initWithUUID:(NSUUID *)uuid
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
if (self = [self init]) {
self.uuid = uuid;
self.successBlock = success;
self.failureBlock = failure;
}
return self;
}
當這個task完成的時候,會調(diào)用我們添加的回調(diào)蝗拿。
- 關(guān)于
AFImageDownloaderMergedTask
晾捏,我們在這里都用的是這種類型的task,其實這個task也很簡單:
@interface AFImageDownloaderMergedTask : NSObject
@property (nonatomic, strong) NSString *URLIdentifier;
@property (nonatomic, strong) NSUUID *identifier;
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
@end
@implementation AFImageDownloaderMergedTask
- (instancetype)initWithURLIdentifier:(NSString *)URLIdentifier identifier:(NSUUID *)identifier task:(NSURLSessionDataTask *)task {
if (self = [self init]) {
self.URLIdentifier = URLIdentifier;
self.task = task;
self.identifier = identifier;
self.responseHandlers = [[NSMutableArray alloc] init];
}
return self;
}
//添加任務(wù)完成回調(diào)
- (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler {
[self.responseHandlers addObject:handler];
}
//移除任務(wù)完成回調(diào)
- (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler {
[self.responseHandlers removeObject:handler];
}
@end
其實就是除了NSURLSessionDataTask
哀托,多加了幾個參數(shù)惦辛,URLIdentifier
和identifier
都是用來標識這個task的,responseHandlers是用來存儲task完成后的回調(diào)的仓手,里面可以存一組胖齐,當任務(wù)完成時候,里面的回調(diào)都會被調(diào)用嗽冒。
- 接著去根據(jù)緩存策略呀伙,去加載緩存,如果有緩存添坊,從
self.imageCache
中返回緩存剿另,否則繼續(xù)往下走。 - 走到這說明沒相同url的task贬蛙,也沒有cache雨女,那么就開始一個新的task,調(diào)用的是
AFUrlSessionManager
里的請求方法生成了一個task(這里我們就不贅述了速客,可以看之前的樓主之前的文章)戚篙。然后做了請求完成的處理。注意溺职,這里處理實在我們一開始初始化的并行queue:self.responseQueue
中的岔擂,這里的響應(yīng)處理是多線程并發(fā)進行的位喂。
1)完成,則調(diào)用如下方法把這個task從全局字典中移除:
//移除task相關(guān)乱灵,用同步串行的形式塑崖,防止移除中出現(xiàn)重復(fù)移除一系列問題
- (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask = nil;
dispatch_sync(self.synchronizationQueue, ^{
mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
});
return mergedTask;
}
2)去循環(huán)這個task的responseHandlers
,調(diào)用它的成功或者失敗的回調(diào)痛倚。
3)并且調(diào)用下面兩個方法规婆,去減少正在請求的任務(wù)數(shù),和開啟下一個任務(wù):
//減少活躍的任務(wù)數(shù)
- (void)safelyDecrementActiveTaskCount {
//回到串行queue去-
dispatch_sync(self.synchronizationQueue, ^{
if (self.activeRequestCount > 0) {
self.activeRequestCount -= 1;
}
});
}
//如果可以蝉稳,則開啟下一個任務(wù)
- (void)safelyStartNextTaskIfNecessary {
//回到串行queue
dispatch_sync(self.synchronizationQueue, ^{
//先判斷并行數(shù)限制
if ([self isActiveRequestCountBelowMaximumLimit]) {
while (self.queuedMergedTasks.count > 0) {
//獲取數(shù)組中第一個task
AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
//如果狀態(tài)是掛起狀態(tài)
if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[self startMergedTask:mergedTask];
break;
}
}
}
});
}
這里需要注意的是抒蚜,跟我們本類的一些數(shù)據(jù)相關(guān)的操作,都是在我們一開始的串行queue中同步進行的耘戚。
4)除此之外嗡髓,如果成功,還把成功請求到的數(shù)據(jù)收津,加到AF自定義的cache中:
//成功根據(jù)request,往cache里添加
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
- 用
NSUUID
生成的唯一標識饿这,去生成AFImageDownloaderResponseHandler
,然后生成一個AFImageDownloaderMergedTask
撞秋,把之前第5步生成的createdTask
和回調(diào)都綁定給這個AF自定義可合并回調(diào)的task长捧,然后這個task加到全局的task映射字典中,key為url:
self.mergedTasks[URLIdentifier] = mergedTask;
- 判斷當前正在下載的任務(wù)是否超過最大并行數(shù)吻贿,如果沒有則開始下載串结,否則先加到等待的數(shù)組中去:
//如果小于最大并行數(shù),則開始任務(wù)下載resume
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
//判斷并行數(shù)限制
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
//開始下載
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
[mergedTask.task resume];
//任務(wù)活躍數(shù)+1
++self.activeRequestCount;
}
//把任務(wù)先加到數(shù)組里
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritizaton) {
//先進先出
case AFImageDownloadPrioritizationFIFO:
[self.queuedMergedTasks addObject:mergedTask];
break;
//后進先出
case AFImageDownloadPrioritizationLIFO:
[self.queuedMergedTasks insertObject:mergedTask atIndex:0];
break;
}
}
- 先判斷并行數(shù)限制廓八,如果小于最大限制奉芦,則開始下載,把當前活躍的request數(shù)量+1剧蹂。
- 如果暫時不能下載声功,被加到等待下載的數(shù)組中去的話,會根據(jù)我們一開始設(shè)置的下載策略宠叼,是先進先出先巴,還是后進先出,去插入這個下載任務(wù)冒冬。
- 最后判斷這個mergeTask是否為空伸蚯。不為空,我們生成了一個
AFImageDownloadReceipt
简烤,綁定了一個UUID剂邮。否則為空返回nil:
if (task) {
//創(chuàng)建一個AFImageDownloadReceipt并返回,里面就多一個receiptID横侦。
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
這個AFImageDownloadReceipt
僅僅是多封裝了一個UUID:
@interface AFImageDownloadReceipt : NSObject
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSUUID *receiptID;
@end
@implementation AFImageDownloadReceipt
- (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task {
if (self = [self init]) {
self.receiptID = receiptID;
self.task = task;
}
return self;
}
這么封裝是為了標識每一個task挥萌,我們后面可以根據(jù)這個AFImageDownloadReceipt
來對task做取消操作绰姻。
這個AFImageDownloader
中最核心的方法基本就講完了,還剩下一些方法沒講引瀑,像前面講到的task的取消的方法:
//根據(jù)AFImageDownloadReceipt來取消任務(wù)狂芋,即對應(yīng)一個響應(yīng)回調(diào)。
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
//拿到url
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
//根據(jù)url拿到task
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
//快速遍歷查找某個下標憨栽,如果返回YES帜矾,則index為當前下標
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
return handler.uuid == imageDownloadReceipt.receiptID;
}];
if (index != NSNotFound) {
//移除響應(yīng)處理
AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
[mergedTask removeResponseHandler:handler];
NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
//并調(diào)用失敗block,原因為取消
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
//如果任務(wù)里的響應(yīng)回調(diào)為空或者狀態(tài)為掛起屑柔,則取消task,并且從字典中移除
if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[mergedTask.task cancel];
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
//根據(jù)URLIdentifier移除task
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
[self.mergedTasks removeObjectForKey:URLIdentifier];
return mergedTask;
}
方法比較簡單屡萤,大家自己看看就好。至此```AFImageDownloader``這個類講完了掸宛。如果大家看的感覺比較繞灭衷,沒關(guān)系,等到最后我們一起來總結(jié)一下旁涤,捋一捋。
我們之前講到AFAutoPurgingImageCache
這個類略過去了迫像,現(xiàn)在我們就來補充一下這個類的相關(guān)內(nèi)容:
首先來講講這個類的作用劈愚,它是AF自定義用來做圖片緩存的。我們來看看它的初始化方法:
- (instancetype)init {
//默認為內(nèi)存100M闻妓,后者為緩存溢出后保留的內(nèi)存
return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
if (self = [super init]) {
//內(nèi)存大小
self.memoryCapacity = memoryCapacity;
self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
//cache的字典
self.cachedImages = [[NSMutableDictionary alloc] init];
NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
//并行的queue
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
//添加通知菌羽,收到內(nèi)存警告的通知
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(removeAllImages)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
初始化方法很簡單,總結(jié)一下:
- 聲明了一個默認的內(nèi)存緩存大小100M由缆,還有一個意思是如果超出100M之后注祖,我們?nèi)デ宄彺妫藭r仍要保留的緩存大小60M均唉。(如果還是不理解是晨,可以看后文,源碼中會講到)
- 創(chuàng)建了一個并行queue舔箭,這個并行queue罩缴,這個類除了初始化以外,所有的方法都是在這個并行queue中調(diào)用的层扶。
- 創(chuàng)建了一個cache字典箫章,我們所有的緩存數(shù)據(jù),都被保存在這個字典中镜会,key為url檬寂,value為
AFCachedImage
。
關(guān)于這個AFCachedImage
戳表,其實就是Image之外封裝了幾個關(guān)于這個緩存的參數(shù)桶至,如下:
@interface AFCachedImage : NSObject
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) NSString *identifier; //url標識
@property (nonatomic, assign) UInt64 totalBytes; //總大小
@property (nonatomic, strong) NSDate *lastAccessDate; //上次獲取時間
@property (nonatomic, assign) UInt64 currentMemoryUsage; //這個參數(shù)沒被用到過
@end
@implementation AFCachedImage
//初始化
-(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
if (self = [self init]) {
self.image = image;
self.identifier = identifier;
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
CGFloat bytesPerPixel = 4.0;
CGFloat bytesPerSize = imageSize.width * imageSize.height;
self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
self.lastAccessDate = [NSDate date];
}
return self;
}
//上次獲取緩存的時間
- (UIImage*)accessImage {
self.lastAccessDate = [NSDate date];
return self.image;
}
- 添加了一個通知昼伴,監(jiān)聽內(nèi)存警告,當發(fā)成內(nèi)存警告塞茅,調(diào)用該方法亩码,移除所有的緩存,并且把當前緩存數(shù)置為0:
//移除所有圖片
- (BOOL)removeAllImages {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
if (self.cachedImages.count > 0) {
[self.cachedImages removeAllObjects];
self.currentMemoryUsage = 0;
removed = YES;
}
});
return removed;
}
注意這個類大量的使用了dispatch_barrier_sync
與dispatch_barrier_async
野瘦,小伙伴們?nèi)绻麑@兩個方法有任何疑惑描沟,可以看看這篇文章:dispatch_barrier_async與dispatch_barrier_sync異同。
1)這里我們可以看到使用了dispatch_barrier_sync
鞭光,這里沒有用鎖吏廉,但是因為使用了dispatch_barrier_sync
,不僅同步了synchronizationQueue
隊列惰许,而且阻塞了當前線程席覆,所以保證了里面執(zhí)行代碼的線程安全問題。
2)在這里其實使用鎖也可以汹买,但是AF在這的處理卻是使用同步的機制來保證線程安全佩伤,或許這跟圖片的加載緩存的使用場景,高頻次有關(guān)系晦毙,在這里使用sync生巡,并不需要在去開辟新的線程,浪費性能见妒,只需要在原有線程孤荣,提交到synchronizationQueue
隊列中,阻塞的執(zhí)行即可须揣。這樣省去大量的開辟線程與使用鎖帶來的性能消耗盐股。(當然這僅僅是我的一個猜測,有不同意見的朋友歡迎討論~)
- 在這里用了
dispatch_barrier_sync
耻卡,因為synchronizationQueue
是個并行queue疯汁,所以在這里不會出現(xiàn)死鎖的問題。 - 關(guān)于保證線程安全的同時劲赠,同步還是異步涛目,與性能方面的考量,可以參考這篇文章:Objc的底層并發(fā)API凛澎。
接著我們來看看這個類最核心的一個方法:
//添加image到cache里
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
//用dispatch_barrier_async霹肝,來同步這個并行隊列
dispatch_barrier_async(self.synchronizationQueue, ^{
//生成cache對象
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
//去之前cache的字典里取
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
//如果有被緩存過
if (previousCachedImage != nil) {
//當前已經(jīng)使用的內(nèi)存大小減去圖片的大小
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
//把新cache的image加上去
self.cachedImages[identifier] = cacheImage;
//加上內(nèi)存大小
self.currentMemoryUsage += cacheImage.totalBytes;
});
//做緩存溢出的清除,清除的是早期的緩存
dispatch_barrier_async(self.synchronizationQueue, ^{
//如果使用的內(nèi)存大于我們設(shè)置的內(nèi)存容量
if (self.currentMemoryUsage > self.memoryCapacity) {
//拿到使用內(nèi)存 - 被清空后首選內(nèi)存 = 需要被清除的內(nèi)存
UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
//拿到所有緩存的數(shù)據(jù)
NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
//根據(jù)lastAccessDate排序 升序塑煎,越晚的越后面
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
ascending:YES];
[sortedImages sortUsingDescriptors:@[sortDescriptor]];
UInt64 bytesPurged = 0;
//移除早期的cache bytesToPurge大小
for (AFCachedImage *cachedImage in sortedImages) {
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
if (bytesPurged >= bytesToPurge) {
break ;
}
}
//減去被清掉的內(nèi)存
self.currentMemoryUsage -= bytesPurged;
}
});
}
看注釋應(yīng)該很容易明白沫换,這個方法做了兩件事:
- 設(shè)置緩存到字典里,并且把對應(yīng)的緩存大小設(shè)置到當前已緩存的數(shù)量屬性中。
- 判斷是緩存超出了我們設(shè)置的最大緩存100M讯赏,如果是的話垮兑,則清除掉部分早時間的緩存,清除到緩存小于我們溢出后保留的內(nèi)存60M以內(nèi)漱挎。
當然在這里更需要說一說的是dispatch_barrier_async
系枪,這里整個類都沒有使用dispatch_async
,所以不存在是為了做一個柵欄磕谅,來同步上下文的線程私爷。其實它在本類中的作用很簡單,就是一個串行執(zhí)行膊夹。
- 講到這衬浑,小伙伴們又疑惑了,既然就是只是為了串行放刨,那為什么我們不用一個串行queue就得了工秩?非得用
dispatch_barrier_async
干嘛?其實小伙伴要是看的仔細进统,就明白了助币,上文我們說過,我們要用dispatch_barrier_sync
來保證線程安全螟碎。如果我們使用串行queue,那么線程是極其容易死鎖的奠支。
還有剩下的幾個方法:
//根據(jù)id獲取圖片
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
//用同步的方式獲取,防止線程安全問題
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
//并且刷新獲取的時間
image = [cachedImage accessImage];
});
return image;
}
//根據(jù)request和additionalIdentifier添加cache
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
[self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
//根據(jù)request和additionalIdentifier移除圖片
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
//根據(jù)request和additionalIdentifier獲取圖片
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
//生成id的方式為Url字符串+additionalIdentifier
- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
NSString *key = request.URL.absoluteString;
if (additionalIdentifier != nil) {
key = [key stringByAppendingString:additionalIdentifier];
}
return key;
}
這幾個方法都很簡單抚芦,大家自己看看就好了,就不贅述了迈螟。至此AFAutoPurgingImageCache
也講完了叉抡,我們還是等到最后再來總結(jié)。
我們繞了一大圈答毫,總算回到了UIImageView+AFNetworking
這個類褥民,現(xiàn)在圖片下載的方法,和緩存的方法都有了洗搂,實現(xiàn)這個類也是水到渠成的事了消返。
我們來看下面我們絕大多數(shù)人很熟悉的方法,看看它的實現(xiàn):
- (void)setImageWithURL:(NSURL *)url {
[self setImageWithURL:url placeholderImage:nil];
}
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
//設(shè)置head耘拇,可接受類型為image
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
上述方法按順序往下調(diào)用撵颊,第二個方法給head的Accept類型設(shè)置為Image。接著調(diào)用到第三個方法惫叛,也是這個類目唯一一個重要的方法:
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
//url為空倡勇,則取消
if ([urlRequest URL] == nil) {
//取消task
[self cancelImageDownloadTask];
//設(shè)置為占位圖
self.image = placeholderImage;
return;
}
//看看設(shè)置的當前的回調(diào)的request和需要請求的request是不是為同一個,是的話為重復(fù)調(diào)用嘉涌,直接返回
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
return;
}
//開始請求前妻熊,先取消之前的task,即解綁回調(diào)
[self cancelImageDownloadTask];
//拿到downloader
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
//拿到cache
id <AFImageRequestCache> imageCache = downloader.imageCache;
//Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
//去獲取cachedImage
if (cachedImage) {
//有的話直接設(shè)置夸浅,并且置空回調(diào)
if (success) {
success(urlRequest, nil, cachedImage);
} else {
self.image = cachedImage;
}
[self clearActiveDownloadInformation];
} else {
//無緩存,如果有占位圖扔役,先設(shè)置
if (placeholderImage) {
self.image = placeholderImage;
}
__weak __typeof(self)weakSelf = self;
NSUUID *downloadID = [NSUUID UUID];
AFImageDownloadReceipt *receipt;
//去下載帆喇,并得到一個receipt,可以用來取消回調(diào)
receipt = [downloader
downloadImageForURLRequest:urlRequest
withReceiptID:downloadID
success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
//判斷receiptID和downloadID是否相同 成功回調(diào)亿胸,設(shè)置圖片
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (success) {
success(request, response, responseObject);
} else if(responseObject) {
strongSelf.image = responseObject;
}
//置空回調(diào)
[strongSelf clearActiveDownloadInformation];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
//失敗有failuerBlock就回調(diào)坯钦,
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
//置空回調(diào)對象
[strongSelf clearActiveDownloadInformation];
}
}];
//賦值
self.af_activeImageDownloadReceipt = receipt;
}
}
這個方法,細節(jié)的地方可以關(guān)注注釋损敷,這里總結(jié)一下做了什么:
1)去判斷url是否為空葫笼,如果為空則取消task,調(diào)用如下方法:
//取消task
- (void)cancelImageDownloadTask {
if (self.af_activeImageDownloadReceipt != nil) {
//取消事件回調(diào)響應(yīng)
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
//置空
[self clearActiveDownloadInformation];
}
}
//置空
- (void)clearActiveDownloadInformation {
self.af_activeImageDownloadReceipt = nil;
}
- 這里注意
cancelImageDownloadTask
中,調(diào)用了self.af_activeImageDownloadReceipt
這么一個屬性拗馒,看看定義的地方:
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@end
@implementation UIImageView (_AFNetworking)
//綁定屬性 AFImageDownloadReceipt路星,就是一個事件響應(yīng)的接受對象,包含一個task诱桂,一個uuid
- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}
//set
- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
我們現(xiàn)在是給UIImageView
添加的一個類目洋丐,所以我們無法直接添加屬性,而是使用的是runtime的方式來生成set和get方法生成了一個AFImageDownloadReceipt
類型的屬性挥等∮丫看過上文應(yīng)該知道這個對象里面就一個task和一個UUID。這個屬性就是我們這次下載任務(wù)相關(guān)聯(lián)的信息肝劲。
2)然后做了一系列判斷迁客,見注釋。
3)然后生成了一個我們之前分析過得AFImageDownloader
辞槐,然后去獲取緩存掷漱,如果有緩存,則直接讀緩存榄檬。還記得AFImageDownloader
里也有一個讀緩存的方法么卜范?那個是和cachePolicy相關(guān)的,而這個是有緩存的話直接讀取鹿榜。不明白的可以回過頭去看看海雪。
4)走到這說明沒緩存了,然后就去用AFImageDownloader
舱殿,我們之前講過的方法奥裸,去請求圖片。完成后沪袭,則調(diào)用成功或者失敗的回調(diào)刺彩,并且置空屬性self.af_activeImageDownloadReceipt
,成功則設(shè)置圖片。
除此之外還有一個取消這次任務(wù)的方法:
//取消task
- (void)cancelImageDownloadTask {
if (self.af_activeImageDownloadReceipt != nil) {
//取消事件回調(diào)響應(yīng)
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
//置空
[self clearActiveDownloadInformation];
}
}
其實也是去調(diào)用我們之前講過的AFImageDownloader
的取消方法创倔。
這個類總共就這么幾行代碼嗡害,就完成了我們幾乎沒有人不用的,設(shè)置ImageView圖片的方法畦攘。當然真正的難點在于AFImageDownloader
和AFAutoPurgingImageCache
霸妹。
接下來我們來總結(jié)一下整個請求圖片,緩存知押,然后設(shè)置圖片的流程:
- 調(diào)用
- (void)setImageWithURL:(NSURL *)url;
時叹螟,我們生成
AFImageDownloader
單例,并替我們請求數(shù)據(jù)台盯。 - 而
AFImageDownloader
會生成一個AFAutoPurgingImageCache
替我們緩存生成的數(shù)據(jù)罢绽。當然我們設(shè)置的時候,給session
的configuration
設(shè)置了一個系統(tǒng)級別的緩存NSUrlCache
,這兩者是互相獨立工作的静盅,互不影響的良价。 - 然后
AFImageDownloader
,就實現(xiàn)下載和協(xié)調(diào)AFAutoPurgingImageCache
去緩存蒿叠,還有一些取消下載的方法明垢。然后通過回調(diào)把數(shù)據(jù)給到我們的類目UIImageView+AFNetworking
,如果成功獲取數(shù)據(jù),則由類目設(shè)置上圖片市咽,整個流程結(jié)束痊银。
經(jīng)過這三個文件:
UIImageView+AFNetworking
、AFImageDownloader
施绎、AFAutoPurgingImageCache
溯革,至此整個設(shè)置網(wǎng)絡(luò)圖片的方法結(jié)束了。
寫在最后:
對于UIKit的總結(jié)谷醉,我們就到此為止了鬓照,其它部分的擴展,小伙伴們可以自行閱讀孤紧,都很簡單,基本上每個類200行左右的代碼拒秘。核心功能基本上都是圍繞
AFURLSessionManager
實現(xiàn)的号显。本來想本篇放在三里面完結(jié),想想還是覺得自己...too young too simple...
但是下一篇應(yīng)該是一個結(jié)束了躺酒,我們會講講AF2.x押蚤,然后詳細總結(jié)一下AF存在的意義。大家任何有疑問或者不同意見的羹应,歡迎評論揽碘,樓主會一一回復(fù)的。求關(guān)注,求贊??雳刺。感謝~~