寫在開頭:
- 大概回憶下,之前我們講了
AFNetworking
整個網絡請求的流程,包括request
的拼接,session
代理的轉發,response
的解析。以及對一些bug
的適配,如果你還沒有看過,可以點這里:
AFNetworking到底做了什么?
AFNetworking到底做了什么(二)? - 除此之外我們還單獨的開了一篇講了AF對
https
的處理:
AFNetworking之于https認證 - 本文將涉及部分AF對UIKit的擴展與圖片下載相關緩存的實現,文章內容相對獨立,如果沒看過前文,也不影響閱讀。
回到正文:
我們來看看AF對UIkit
的擴展:
一共如上這個多類,下面我們開始著重講其中兩個UIKit的擴展:
- 一個是我們網絡請求時狀態欄的小菊花。
- 一個是我們幾乎都用到過請求網絡圖片的如下一行方法:
- (void)setImageWithURL:(NSURL *)url ;
我們開始吧:
1.AFNetworkActivityIndicatorManager
這個類的作用相當簡單,就是當網絡請求的時候,狀態欄上的小菊花就會開始轉:
需要的代碼也很簡單,只需在你需要它的位置中(比如AppDelegate)導入類,并加一行代碼即可:
#import "AFNetworkActivityIndicatorManager.h"
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
接下來我們來講講這個類的實現:
這個類的實現也非常簡單,還記得我們之前講的AF對
NSURLSessionTask
中做了一個Method Swizzling嗎?大意是把它的resume
和suspend
方法做了一個替換,在原有實現的基礎上添加了一個通知的發送。這個類就是基于這兩個通知和task完成的通知來實現的。
首先我們來看看它的初始化方法:
+ (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;
}
//設置狀態為沒有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;
//結束延遲
self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay;
return self;
}
- 初始化如上,設置了一個state,這個state是一個枚舉:
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
//沒有請求
AFNetworkActivityManagerStateNotActive,
//請求延遲開始
AFNetworkActivityManagerStateDelayingStart,
//請求進行中
AFNetworkActivityManagerStateActive,
//請求延遲結束
AFNetworkActivityManagerStateDelayingEnd
};
這個state一共如上4種狀態,其中兩種應該很好理解,而延遲開始和延遲結束怎么理解呢?
原來這是AF對請求菊花顯示做的一個優化處理,試問如果一個請求時間很短,那么菊花很可能閃一下就結束了。如果很多請求過來,那么菊花會不停的閃啊閃,這顯然并不是我們想要的效果。
所以多了這兩個參數:
1)在一個請求開始的時候,我延遲一會在去轉菊花,如果在這延遲時間內,請求結束了,那么我就不需要去轉菊花了。
2)但是一旦轉菊花開始,哪怕很短請求就結束了,我們還是會去轉一個時間再去結束,這時間就是延遲結束的時間。緊接著我們監聽了三個通知,用來監聽當前正在進行的網絡請求的狀態。
然后設置了我們前面提到的這個轉菊花延遲開始和延遲結束的時間,這兩個默認值如下:
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
接著我們來看看三個通知觸發調用的方法:
//請求開始
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
//增加請求活躍數
[self incrementActivityCount];
}
}
//請求結束
- (void)networkRequestDidFinish:(NSNotification *)notification {
//AFNetworkRequestFromNotification(notification)返回這個通知的request,用來判斷request是否是有效的
if ([AFNetworkRequestFromNotification(notification) URL]) {
//減少請求活躍數
[self decrementActivityCount];
}
}
方法很簡單,就是開始的時候增加了請求活躍數,結束則減少。調用了如下兩個方法進行加減:
//增加請求活躍數
- (void)incrementActivityCount {
//活躍的網絡數+1,并手動發送KVO
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
//主線程去做
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
//減少請求活躍數
- (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];
});
}
方法做了什么應該很容易看明白,這里需要注意的是,task的幾個狀態的通知,是會在多線程的環境下發送過來的。所以這里對活躍數的加減,都用了@synchronized
這種方式的鎖,進行了線程保護。然后回到主線程調用了updateCurrentStateForNetworkActivityChange
我們接著來看看這個方法:
- (void)updateCurrentStateForNetworkActivityChange {
//如果是允許小菊花
if (self.enabled) {
switch (self.currentState) {
//不活躍
case AFNetworkActivityManagerStateNotActive:
//判斷活躍數,大于0為YES
if (self.isNetworkActivityOccurring) {
//設置狀態為延遲開始
[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;
}
}
}
- 這個方法先是判斷了我們一開始設置是否需要菊花的
self.enabled
,如果需要,才執行。 - 這里主要是根據當前的狀態,來判斷下一個狀態應該是什么。其中有這么一個屬性
self.isNetworkActivityOccurring
:
//判斷是否活躍
- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}
那么這個方法應該不難理解了。
這個類復寫了currentState的set方法,每當我們改變這個state,就會觸發set方法,而怎么該轉菊花也在該方法中:
//設置當前小菊花狀態
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
@synchronized(self) {
if (_currentState != currentState) {
//KVO
[self willChangeValueForKey:@"currentState"];
_currentState = currentState;
switch (currentState) {
//如果為不活躍
case AFNetworkActivityManagerStateNotActive:
//取消兩個延遲用的timer
[self cancelActivationDelayTimer];
[self cancelCompletionDelayTimer];
//設置小菊花不可見
[self setNetworkActivityIndicatorVisible:NO];
break;
case AFNetworkActivityManagerStateDelayingStart:
//開啟一個定時器延遲去轉菊花
[self startActivationDelayTimer];
break;
//如果是活躍狀態
case AFNetworkActivityManagerStateActive:
//取消延遲完成的timer
[self cancelCompletionDelayTimer];
//開始轉菊花
[self setNetworkActivityIndicatorVisible:YES];
break;
//延遲完成狀態
case AFNetworkActivityManagerStateDelayingEnd:
//開啟延遲完成timer
[self startCompletionDelayTimer];
break;
}
}
[self didChangeValueForKey:@"currentState"];
}
}
這個set方法就是這個類最核心的方法了。它的作用如下:
- 這里根據當前狀態,是否需要開始執行一個延遲開始或者延遲完成,又或者是否需要取消這兩個延遲。
- 還判斷了,是否需要去轉狀態欄的菊花,調用了
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根據該Bool,去控制狀態欄小菊花是否顯示
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
}
}
}
- 這個方法就是用來控制菊花是否轉。并且支持一個自定義的Block,我們可以自己去拿到這個菊花是否應該轉的狀態值,去做一些自定義的處理。
- 如果我們沒有實現這個Block,則調用:
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
去轉菊花。
回到state的set方法中,我們除了控制菊花去轉,還調用了以下4個方法:
//開始任務到結束的時間,默認為1秒,如果1秒就結束,那么不轉菊花,延遲去開始轉
- (void)startActivationDelayTimer {
//只執行一次
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
//添加到主線程runloop去觸發
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
//完成任務到下一個任務開始,默認為0.17秒,如果0.17秒就開始下一個,那么不停 延遲去結束菊花轉
- (void)startCompletionDelayTimer {
//先取消之前的
[self.completionDelayTimer invalidate];
//延遲執行讓菊花不在轉
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個方法分別是開始延遲執行一個方法,和結束的時候延遲執行一個方法,和對應這兩個方法的取消。其作用,注釋應該很容易理解。
我們繼續往下看,這兩個延遲調用的到底是什么:
- (void)activationDelayTimerFired {
//活躍狀態,即活躍數大于1才轉
if (self.networkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
} else {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
- (void)completionDelayTimerFired {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
一個開始,一個完成調用,都設置了不同的currentState的值,又回到之前state
的set
方法中了。
至此這個AFNetworkActivityIndicatorManager
類就講完了,代碼還是相當簡單明了的。
2.UIImageView+AFNetworking
接下來我們來講一個我們經常用的方法,這個方法的實現類是: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去異步的請求一張圖片,并且可以設置一張占位圖。
- 第3個方法設置一張圖,并且可以拿到成功和失敗的回調。
- 第4個方法,可以取消當前的圖片設置請求。
無論SDWebImage
,還是YYKit
,或者AF
,都實現了這么個類目。
AF關于這個類目UIImageView+AFNetworking
的實現,依賴于這么兩個類:AFImageDownloader
,AFAutoPurgingImageCache
。
當然AFImageDownloader
中,關于圖片數據請求的部分,還是使用AFURLSessionManager
來實現的。
接下來我們就來看看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;
//是否允許蜂窩網絡,手機網
configuration.allowsCellularAccess = YES;
//默認超時
configuration.timeoutIntervalForRequest = 60.0;
//設置的圖片緩存對象
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
該類為單例,上述方法中,創建了一個sessionManager
,這個sessionManager
將用于我們之后的網絡請求。從這里我們可以看到,這個類的網絡請求都是基于之前AF自己封裝的AFHTTPSessionManager
。
- 在這里初始化了一系列的對象,需要講一下的是
AFImageDownloadPrioritizationFIFO
,這個一個枚舉值:
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
//先進先出
AFImageDownloadPrioritizationFIFO,
//后進先出
AFImageDownloadPrioritizationLIFO
};
這個枚舉值代表著,一堆圖片下載,執行任務的順序。
- 還有一個
AFAutoPurgingImageCache
的創建,這個類是AF做圖片緩存用的。這里我們暫時就這么理解它,講完當前類,我們再來補充它。 - 除此之外,我們還看到一個cache:
configuration.URLCache = [AFImageDownloader defaultURLCache];
//設置一個系統緩存,內存緩存為20M,磁盤緩存為150M,
//這個是系統級別維護的緩存。
+ (NSURLCache *)defaultURLCache {
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:150 * 1024 * 1024
diskPath:@"com.alamofire.imagedownloader"];
}
大家看到這可能迷惑了,怎么這么多cache,那AF做圖片緩存到底用哪個呢?答案是AF自己控制的圖片緩存用AFAutoPurgingImageCache
,而NSUrlRequest
的緩存由它自己內部根據策略去控制,用的是NSURLCache
,不歸AF處理,只需在configuration中設置上即可。
- 那么看到這有些小伙伴又要問了,為什么不直接用
NSURLCache
,還要自定義一個AFAutoPurgingImageCache
呢?原來是因為NSURLCache
的諸多限制,例如只支持get請求等等。而且因為是系統維護的,我們自己的可控度不強,并且如果需要做一些自定義的緩存處理,無法實現。 - 更多關于
NSURLCache
的內容,大家可以自行查閱。
接著上面的方法調用到這個最終的初始化方法中:
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache {
if (self = [super init]) {
//持有
self.sessionManager = sessionManager;
//定義下載任務的順序,默認FIFO,先進先出-隊列模式,還有后進先出-棧模式
self.downloadPrioritizaton = downloadPrioritization;
//最大的下載數
self.maximumActiveDownloads = maximumActiveDownloads;
//自定義的cache
self.imageCache = imageCache;
//隊列中的任務,待執行的
self.queuedMergedTasks = [[NSMutableArray alloc] init];
//合并的任務,所有任務的字典
self.mergedTasks = [[NSMutableDictionary alloc] init];
//活躍的request數
self.activeRequestCount = 0;
//用UUID來拼接名字
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
//創建一個串行的queue
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
//創建并行queue
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
這邊初始化了一些屬性,這些屬性跟著注釋看應該很容易明白其作用。主要需要注意的就是,這里創建了兩個queue:一個串行的請求queue,和一個并行的響應queue。
- 這個串行queue,是用來做內部生成task等等一系列業務邏輯的。它保證了我們在這些邏輯處理中的線程安全問題(迷惑的接著往下看)。
- 這個并行queue,被用來做網絡請求完成的數據回調。
接下來我們來看看它的創建請求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;
}
//如果這個任務已經存在,則添加成功失敗Block,然后直接返回,即一個url用一個request,可以響應好幾個block
//從自己task字典中根據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;
}
//根據request的緩存策略,加載緩存
switch (request.cachePolicy) {
//這3種情況都會去加載緩存
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
//從cache中根據request拿數據
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的去請求,注意,只是創建task,還是掛起狀態
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
//在responseQueue中回調數據,初始化為并行queue
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
//拿到當前的task
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
//如果之前的task數組中,有這個請求的任務task,則從數組中移除
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
//安全的移除,并返回當前被移除的AF task
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
//請求錯誤
if (error) {
//去遍歷task所有響應的處理
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
//主線程,調用失敗的Block
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
});
}
}
} else {
//成功根據request,往cache里添加
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
//調用成功Block
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
});
}
}
}
}
//減少活躍的任務數
[strongSelf safelyDecrementActiveTaskCount];
[strongSelf safelyStartNextTaskIfNecessary];
});
}];
// 4) Store the response handler for use when the request completes
//創建handler
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
//創建task
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
//添加handler
[mergedTask addResponseHandler:handler];
//往當前任務字典里添加任務
self.mergedTasks[URLIdentifier] = mergedTask;
// 5) Either start the request or enqueue it depending on the current active request count
//如果小于,則開始任務下載resume
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
//拿到最終生成的task
task = mergedTask.task;
});
if (task) {
//創建一個AFImageDownloadReceipt并返回,里面就多一個receiptID。
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
}
就這么一個非常非常長的方法,這個方法執行的內容都是在我們之前創建的串行queue中,同步的執行的,這是因為這個方法絕大多數的操作都是需要線程安全的??梢詫χ创a和注釋來看,我們在這講下它做了什么:
- 首先做了一個url的判斷,如果為空則返回失敗Block。
- 判斷這個需要請求的url,是不是已經被生成的task中,如果是的話,則多添加一個回調處理就可以。回調處理對象為
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
//初始化回調對象
- (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完成的時候,會調用我們添加的回調。
- 關于
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;
}
//添加任務完成回調
- (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler {
[self.responseHandlers addObject:handler];
}
//移除任務完成回調
- (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler {
[self.responseHandlers removeObject:handler];
}
@end
其實就是除了NSURLSessionDataTask
,多加了幾個參數,URLIdentifier
和identifier
都是用來標識這個task的,responseHandlers是用來存儲task完成后的回調的,里面可以存一組,當任務完成時候,里面的回調都會被調用。
- 接著去根據緩存策略,去加載緩存,如果有緩存,從
self.imageCache
中返回緩存,否則繼續往下走。 - 走到這說明沒相同url的task,也沒有cache,那么就開始一個新的task,調用的是
AFUrlSessionManager
里的請求方法生成了一個task(這里我們就不贅述了,可以看之前的樓主之前的文章)。然后做了請求完成的處理。注意,這里處理實在我們一開始初始化的并行queue:self.responseQueue
中的,這里的響應處理是多線程并發進行的。
1)完成,則調用如下方法把這個task從全局字典中移除:
//移除task相關,用同步串行的形式,防止移除中出現重復移除一系列問題
- (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask = nil;
dispatch_sync(self.synchronizationQueue, ^{
mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
});
return mergedTask;
}
2)去循環這個task的responseHandlers
,調用它的成功或者失敗的回調。
3)并且調用下面兩個方法,去減少正在請求的任務數,和開啟下一個任務:
//減少活躍的任務數
- (void)safelyDecrementActiveTaskCount {
//回到串行queue去-
dispatch_sync(self.synchronizationQueue, ^{
if (self.activeRequestCount > 0) {
self.activeRequestCount -= 1;
}
});
}
//如果可以,則開啟下一個任務
- (void)safelyStartNextTaskIfNecessary {
//回到串行queue
dispatch_sync(self.synchronizationQueue, ^{
//先判斷并行數限制
if ([self isActiveRequestCountBelowMaximumLimit]) {
while (self.queuedMergedTasks.count > 0) {
//獲取數組中第一個task
AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
//如果狀態是掛起狀態
if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[self startMergedTask:mergedTask];
break;
}
}
}
});
}
這里需要注意的是,跟我們本類的一些數據相關的操作,都是在我們一開始的串行queue中同步進行的。
4)除此之外,如果成功,還把成功請求到的數據,加到AF自定義的cache中:
//成功根據request,往cache里添加
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
- 用
NSUUID
生成的唯一標識,去生成AFImageDownloaderResponseHandler
,然后生成一個AFImageDownloaderMergedTask
,把之前第5步生成的createdTask
和回調都綁定給這個AF自定義可合并回調的task,然后這個task加到全局的task映射字典中,key為url:
self.mergedTasks[URLIdentifier] = mergedTask;
- 判斷當前正在下載的任務是否超過最大并行數,如果沒有則開始下載,否則先加到等待的數組中去:
//如果小于最大并行數,則開始任務下載resume
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
//判斷并行數限制
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
//開始下載
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
[mergedTask.task resume];
//任務活躍數+1
++self.activeRequestCount;
}
//把任務先加到數組里
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritizaton) {
//先進先出
case AFImageDownloadPrioritizationFIFO:
[self.queuedMergedTasks addObject:mergedTask];
break;
//后進先出
case AFImageDownloadPrioritizationLIFO:
[self.queuedMergedTasks insertObject:mergedTask atIndex:0];
break;
}
}
- 先判斷并行數限制,如果小于最大限制,則開始下載,把當前活躍的request數量+1。
- 如果暫時不能下載,被加到等待下載的數組中去的話,會根據我們一開始設置的下載策略,是先進先出,還是后進先出,去插入這個下載任務。
- 最后判斷這個mergeTask是否為空。不為空,我們生成了一個
AFImageDownloadReceipt
,綁定了一個UUID。否則為空返回nil:
if (task) {
//創建一個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,我們后面可以根據這個AFImageDownloadReceipt
來對task做取消操作。
這個AFImageDownloader
中最核心的方法基本就講完了,還剩下一些方法沒講,像前面講到的task的取消的方法:
//根據AFImageDownloadReceipt來取消任務,即對應一個響應回調。
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
//拿到url
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
//根據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) {
//移除響應處理
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];
//并調用失敗block,原因為取消
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
//如果任務里的響應回調為空或者狀態為掛起,則取消task,并且從字典中移除
if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[mergedTask.task cancel];
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
//根據URLIdentifier移除task
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
[self.mergedTasks removeObjectForKey:URLIdentifier];
return mergedTask;
}
方法比較簡單,大家自己看看就好。至此```AFImageDownloader``這個類講完了。如果大家看的感覺比較繞,沒關系,等到最后我們一起來總結一下,捋一捋。
我們之前講到AFAutoPurgingImageCache
這個類略過去了,現在我們就來補充一下這個類的相關內容:
首先來講講這個類的作用,它是AF自定義用來做圖片緩存的。我們來看看它的初始化方法:
- (instancetype)init {
//默認為內存100M,后者為緩存溢出后保留的內存
return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
if (self = [super init]) {
//內存大小
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);
//添加通知,收到內存警告的通知
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(removeAllImages)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
初始化方法很簡單,總結一下:
- 聲明了一個默認的內存緩存大小100M,還有一個意思是如果超出100M之后,我們去清除緩存,此時仍要保留的緩存大小60M。(如果還是不理解,可以看后文,源碼中會講到)
- 創建了一個并行queue,這個并行queue,這個類除了初始化以外,所有的方法都是在這個并行queue中調用的。
- 創建了一個cache字典,我們所有的緩存數據,都被保存在這個字典中,key為url,value為
AFCachedImage
。
關于這個AFCachedImage
,其實就是Image之外封裝了幾個關于這個緩存的參數,如下:
@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; //這個參數沒被用到過
@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;
}
- 添加了一個通知,監聽內存警告,當發成內存警告,調用該方法,移除所有的緩存,并且把當前緩存數置為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
,小伙伴們如果對這兩個方法有任何疑惑,可以看看這篇文章:dispatch_barrier_async與dispatch_barrier_sync異同。
1)這里我們可以看到使用了dispatch_barrier_sync
,這里沒有用鎖,但是因為使用了dispatch_barrier_sync
,不僅同步了synchronizationQueue
隊列,而且阻塞了當前線程,所以保證了里面執行代碼的線程安全問題。
2)在這里其實使用鎖也可以,但是AF在這的處理卻是使用同步的機制來保證線程安全,或許這跟圖片的加載緩存的使用場景,高頻次有關系,在這里使用sync,并不需要在去開辟新的線程,浪費性能,只需要在原有線程,提交到synchronizationQueue
隊列中,阻塞的執行即可。這樣省去大量的開辟線程與使用鎖帶來的性能消耗。(當然這僅僅是我的一個猜測,有不同意見的朋友歡迎討論~)
- 在這里用了
dispatch_barrier_sync
,因為synchronizationQueue
是個并行queue,所以在這里不會出現死鎖的問題。 - 關于保證線程安全的同時,同步還是異步,與性能方面的考量,可以參考這篇文章:Objc的底層并發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) {
//當前已經使用的內存大小減去圖片的大小
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
//把新cache的image加上去
self.cachedImages[identifier] = cacheImage;
//加上內存大小
self.currentMemoryUsage += cacheImage.totalBytes;
});
//做緩存溢出的清除,清除的是早期的緩存
dispatch_barrier_async(self.synchronizationQueue, ^{
//如果使用的內存大于我們設置的內存容量
if (self.currentMemoryUsage > self.memoryCapacity) {
//拿到使用內存 - 被清空后首選內存 = 需要被清除的內存
UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
//拿到所有緩存的數據
NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
//根據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 ;
}
}
//減去被清掉的內存
self.currentMemoryUsage -= bytesPurged;
}
});
}
看注釋應該很容易明白,這個方法做了兩件事:
- 設置緩存到字典里,并且把對應的緩存大小設置到當前已緩存的數量屬性中。
- 判斷是緩存超出了我們設置的最大緩存100M,如果是的話,則清除掉部分早時間的緩存,清除到緩存小于我們溢出后保留的內存60M以內。
當然在這里更需要說一說的是dispatch_barrier_async
,這里整個類都沒有使用dispatch_async
,所以不存在是為了做一個柵欄,來同步上下文的線程。其實它在本類中的作用很簡單,就是一個串行執行。
- 講到這,小伙伴們又疑惑了,既然就是只是為了串行,那為什么我們不用一個串行queue就得了?非得用
dispatch_barrier_async
干嘛?其實小伙伴要是看的仔細,就明白了,上文我們說過,我們要用dispatch_barrier_sync
來保證線程安全。如果我們使用串行queue,那么線程是極其容易死鎖的。
還有剩下的幾個方法:
//根據id獲取圖片
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
//用同步的方式獲取,防止線程安全問題
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
//并且刷新獲取的時間
image = [cachedImage accessImage];
});
return image;
}
//根據request和additionalIdentifier添加cache
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
[self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
//根據request和additionalIdentifier移除圖片
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
//根據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
也講完了,我們還是等到最后再來總結。
我們繞了一大圈,總算回到了UIImageView+AFNetworking
這個類,現在圖片下載的方法,和緩存的方法都有了,實現這個類也是水到渠成的事了。
我們來看下面我們絕大多數人很熟悉的方法,看看它的實現:
- (void)setImageWithURL:(NSURL *)url {
[self setImageWithURL:url placeholderImage:nil];
}
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
//設置head,可接受類型為image
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
上述方法按順序往下調用,第二個方法給head的Accept類型設置為Image。接著調用到第三個方法,也是這個類目唯一一個重要的方法:
- (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];
//設置為占位圖
self.image = placeholderImage;
return;
}
//看看設置的當前的回調的request和需要請求的request是不是為同一個,是的話為重復調用,直接返回
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
return;
}
//開始請求前,先取消之前的task,即解綁回調
[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) {
//有的話直接設置,并且置空回調
if (success) {
success(urlRequest, nil, cachedImage);
} else {
self.image = cachedImage;
}
[self clearActiveDownloadInformation];
} else {
//無緩存,如果有占位圖,先設置
if (placeholderImage) {
self.image = placeholderImage;
}
__weak __typeof(self)weakSelf = self;
NSUUID *downloadID = [NSUUID UUID];
AFImageDownloadReceipt *receipt;
//去下載,并得到一個receipt,可以用來取消回調
receipt = [downloader
downloadImageForURLRequest:urlRequest
withReceiptID:downloadID
success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
//判斷receiptID和downloadID是否相同 成功回調,設置圖片
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (success) {
success(request, response, responseObject);
} else if(responseObject) {
strongSelf.image = responseObject;
}
//置空回調
[strongSelf clearActiveDownloadInformation];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
//失敗有failuerBlock就回調,
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
//置空回調對象
[strongSelf clearActiveDownloadInformation];
}
}];
//賦值
self.af_activeImageDownloadReceipt = receipt;
}
}
這個方法,細節的地方可以關注注釋,這里總結一下做了什么:
1)去判斷url是否為空,如果為空則取消task,調用如下方法:
//取消task
- (void)cancelImageDownloadTask {
if (self.af_activeImageDownloadReceipt != nil) {
//取消事件回調響應
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
//置空
[self clearActiveDownloadInformation];
}
}
//置空
- (void)clearActiveDownloadInformation {
self.af_activeImageDownloadReceipt = nil;
}
- 這里注意
cancelImageDownloadTask
中,調用了self.af_activeImageDownloadReceipt
這么一個屬性,看看定義的地方:
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@end
@implementation UIImageView (_AFNetworking)
//綁定屬性 AFImageDownloadReceipt,就是一個事件響應的接受對象,包含一個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
我們現在是給UIImageView
添加的一個類目,所以我們無法直接添加屬性,而是使用的是runtime的方式來生成set和get方法生成了一個AFImageDownloadReceipt
類型的屬性??催^上文應該知道這個對象里面就一個task和一個UUID。這個屬性就是我們這次下載任務相關聯的信息。
2)然后做了一系列判斷,見注釋。
3)然后生成了一個我們之前分析過得AFImageDownloader
,然后去獲取緩存,如果有緩存,則直接讀緩存。還記得AFImageDownloader
里也有一個讀緩存的方法么?那個是和cachePolicy相關的,而這個是有緩存的話直接讀取。不明白的可以回過頭去看看。
4)走到這說明沒緩存了,然后就去用AFImageDownloader
,我們之前講過的方法,去請求圖片。完成后,則調用成功或者失敗的回調,并且置空屬性self.af_activeImageDownloadReceipt
,成功則設置圖片。
除此之外還有一個取消這次任務的方法:
//取消task
- (void)cancelImageDownloadTask {
if (self.af_activeImageDownloadReceipt != nil) {
//取消事件回調響應
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
//置空
[self clearActiveDownloadInformation];
}
}
其實也是去調用我們之前講過的AFImageDownloader
的取消方法。
這個類總共就這么幾行代碼,就完成了我們幾乎沒有人不用的,設置ImageView圖片的方法。當然真正的難點在于AFImageDownloader
和AFAutoPurgingImageCache
。
接下來我們來總結一下整個請求圖片,緩存,然后設置圖片的流程:
- 調用
- (void)setImageWithURL:(NSURL *)url;
時,我們生成
AFImageDownloader
單例,并替我們請求數據。 - 而
AFImageDownloader
會生成一個AFAutoPurgingImageCache
替我們緩存生成的數據。當然我們設置的時候,給session
的configuration
設置了一個系統級別的緩存NSUrlCache
,這兩者是互相獨立工作的,互不影響的。 - 然后
AFImageDownloader
,就實現下載和協調AFAutoPurgingImageCache
去緩存,還有一些取消下載的方法。然后通過回調把數據給到我們的類目UIImageView+AFNetworking
,如果成功獲取數據,則由類目設置上圖片,整個流程結束。
經過這三個文件:
UIImageView+AFNetworking
、AFImageDownloader
、AFAutoPurgingImageCache
,至此整個設置網絡圖片的方法結束了。
寫在最后:
對于UIKit的總結,我們就到此為止了,其它部分的擴展,小伙伴們可以自行閱讀,都很簡單,基本上每個類200行左右的代碼。核心功能基本上都是圍繞
AFURLSessionManager
實現的。本來想本篇放在三里面完結,想想還是覺得自己...too young too simple...
但是下一篇應該是一個結束了,我們會講講AF2.x,然后詳細總結一下AF存在的意義。大家任何有疑問或者不同意見的,歡迎評論,樓主會一一回復的。求關注,求贊??。感謝~~