該文章閱讀的AFNetworking的版本為3.2.0。
該類用於圖片的下載
1.列舉
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
AFImageDownloadPrioritizationFIFO,
AFImageDownloadPrioritizationLIFO
};
複製程式碼
該列舉定義了圖片下載的優先順序:
AFImageDownloadPrioritizationFIFO
表示下載圖片時按照先進先出的原則
AFImageDownloadPrioritizationLIFO
表示下載圖片時按照後進先出的原則
2.公共私有類
2.1.AFImageDownloadReceipt類
這個類是一個公共類,它是對一個task
的封裝,並用UUID作為識別符號。
2.1.1.介面
/**
AFImageDownloader建立的task
*/
@property (nonatomic, strong) NSURLSessionDataTask *task;
/**
識別符號
*/
@property (nonatomic, strong) NSUUID *receiptID;
複製程式碼
2.1.2.實現
實現非常簡單,只有一個自定義的初始化方法,將task
和識別符號進行儲存。
- (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task {
if (self = [self init]) {
self.receiptID = receiptID;
self.task = task;
}
return self;
}
複製程式碼
2.2.AFImageDownloaderResponseHandler類
這個類是封裝task
的回撥block,並用UUID作為識別符號
2.2.1.介面
/**
手機的UUID
*/
@property (nonatomic, strong) NSUUID *uuid;
/**
成功回撥block
*/
@property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);
/**
失敗回撥block
*/
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
複製程式碼
2.2.1.實現
它的實現同樣也是很簡單,一個自定義初始化方法,一個列印資料方法
- (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;
}
- (NSString *)description {
// 定製列印資料
return [NSString stringWithFormat: @"<AFImageDownloaderResponseHandler>UUID: %@", [self.uuid UUIDString]];
}
複製程式碼
2.3.AFImageDownloaderMergedTask類
這個類是將同樣的任務進項合併,但保留回撥block,同樣是以UUID作為識別符號
2.3.1.介面
/**
圖片連結識別符號
*/
@property (nonatomic, strong) NSString *URLIdentifier;
/**
手機的UUID
*/
@property (nonatomic, strong) NSUUID *identifier;
/**
下載任務
*/
@property (nonatomic, strong) NSURLSessionDataTask *task;
/**
響應回撥陣列
*/
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
複製程式碼
2.3.2.實現
/**
自定義初始化方法
*/
- (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];
}
複製程式碼
3.AFImageDownloader類的介面
3.1.屬性
/**
圖片快取物件,預設是AFAutoPurgingImageCache物件
*/
@property (nonatomic, strong, nullable) id <AFImageRequestCache> imageCache;
/**
用來下載圖片的網路會話管理物件,預設是AFImageResponseSerializer物件
*/
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
/**
下載任務佇列的優先順序,預設是先進先出
*/
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton;
複製程式碼
3.2.方法
/**
獲取圖片下載器單例物件
*/
+ (instancetype)defaultInstance;
/**
獲取預設的URL快取物件
*/
+ (NSURLCache *)defaultURLCache;
/**
獲取預設的網路會話配置物件
*/
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration;
/**
預設的初始化方法
*/
- (instancetype)init;
/**
指定網路會話配置物件的初始化方法
*/
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
/**
以指定網路會話管理者、下載優先順序、最大下載數量和圖片快取物件的初始化方法
*/
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(nullable id <AFImageRequestCache>)imageCache;
/**
以指定請求建立一個圖片下載的task。但是,如果相同的任務已經在佇列中或者正在下載,就不會建立task,而是直接把成功和失敗回撥與原task相關聯
*/
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
/**
以指定請求和識別符號建立一個圖片下載的task。但是,如果相同的任務已經在佇列中或者正在下載,就不會建立task,而是直接把成功和失敗回撥與原task相關聯。
*/
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
/**
取消下載任務,如果任務在佇列中,任務會被取消;如果任務正在執行或者已完成,成功和失敗回撥會被刪除。
*/
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt;
複製程式碼
4.AFImageDownloader類的類擴充套件
/**
序列佇列
*/
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
/**
響應佇列
*/
@property (nonatomic, strong) dispatch_queue_t responseQueue;
/**
最大下載數
*/
@property (nonatomic, assign) NSInteger maximumActiveDownloads;
/**
當前活動請求數
*/
@property (nonatomic, assign) NSInteger activeRequestCount;
/**
佇列中的任務
*/
@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;
/**
儲存任務和其識別符號
*/
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;
複製程式碼
5.AFImageDownloader類的實現
- 公共方法的實現
+ (NSURLCache *)defaultURLCache {
// 這個地方作者解釋說,在某些版本中自定義NSURLCache會導致崩潰。從下面的程式碼可以看出,當系統版本比iOS8.2小的時候用系統預設的快取方法,否則就是記憶體20M,硬碟150M的自定義快取容量
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.2" options:NSNumericSearch] == NSOrderedAscending) {
return [NSURLCache sharedURLCache];
}
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:150 * 1024 * 1024
diskPath:@"com.alamofire.imagedownloader"];
}
+ (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;
}
- (instancetype)init {
// 例項化網路會話配置類物件並以之為引數呼叫下面的方法
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
return [self initWithSessionConfiguration:defaultConfiguration];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
// 利用引數例項化AFHTTPSessionManager物件,然後以之為引數呼叫下面的方法
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
return [self initWithSessionManager:sessionManager
downloadPrioritization:AFImageDownloadPrioritizationFIFO
maximumActiveDownloads:4
imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache {
if (self = [super init]) {
// 用屬性記錄引數
self.sessionManager = sessionManager;
self.downloadPrioritizaton = downloadPrioritization;
self.maximumActiveDownloads = maximumActiveDownloads;
self.imageCache = imageCache;
// 初始化屬性
self.queuedMergedTasks = [[NSMutableArray alloc] init];
self.mergedTasks = [[NSMutableDictionary alloc] init];
self.activeRequestCount = 0;
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
+ (instancetype)defaultInstance {
// 生成單例物件
static AFImageDownloader *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success
failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure {
// 以識別符號為UUID呼叫下面的方法
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 {
// 生成臨時變數
__block NSURLSessionDataTask *task = nil;
// 同步序列佇列
dispatch_sync(self.synchronizationQueue, ^{
// 如果沒有圖片連線就返回並回撥錯誤資訊
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
// 1) 如果已經傳送過這個請求,就直接把回撥放到陣列中等待回撥,task直接賦值,不再生成,然後返回
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
// 2) 如果快取策略允許就從快取中載入影象
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
// 從圖片快取物件中獲取圖片
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;
}
// 3) 建立請求並設定認證,驗證和響應序列化
NSUUID *mergedTaskIdentifier = [NSUUID UUID];
NSURLSessionDataTask *createdTask;
__weak __typeof__(self) weakSelf = self;
createdTask = [self.sessionManager
dataTaskWithRequest:request
uploadProgress:nil
downloadProgress:nil
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
// 獲取到URLIdentifier對應的任務物件
AFImageDownloaderMergedTask *mergedTask = strongSelf.mergedTasks[URLIdentifier];
// 如果是同一個任務物件
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
// 就從任務佇列中刪除
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
// 如果出錯就找出對應的錯誤回撥block進行回撥
if (error) {
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
});
}
}
// 如果不是同一個任務
} else {
// 把下載完成的圖片新增到快取中
if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
[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) 儲存響應處理回撥以在請求完成時使用
// 根據識別符號、成功回撥block和失敗回撥block生成圖片下載響應物件
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
// 根據圖片連結、識別符號和任務生成圖片下載合併任務物件
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
// 將生成的圖片下載響應物件新增到圖片下載合併任務物件中
[mergedTask addResponseHandler:handler];
// 根據圖片連結儲存其對應的圖片下載合併任務物件
self.mergedTasks[URLIdentifier] = mergedTask;
// 5) 根據當前的活動請求計數啟動請求或將其排入佇列
// 如果沒達到請求數量上限,就啟動任務,否則就排隊
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
task = mergedTask.task;
});
// 生成任務封裝類並返回
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
}
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
// 通過圖片封裝物件獲取到任務的連結
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
// 通過連結獲取到任務
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
// 通過識別符號獲取到響應物件在陣列中的索引
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];
// 生成錯誤物件並回撥錯誤block
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];
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
// 如果回撥物件為空,並且任務已經暫停。就取消任務,並移除掉對應的任務合併物件
if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[mergedTask.task cancel];
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
複製程式碼
- 私有方法
/**
通過識別符號安全移除合併任務物件
*/
- (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
// 序列佇列同步執行移除合併任務物件
__block AFImageDownloaderMergedTask *mergedTask = nil;
dispatch_sync(self.synchronizationQueue, ^{
mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
});
return mergedTask;
}
/**
通過識別符號移除合併任務物件,這個方法只能在self.synchronizationQueue佇列中安全呼叫
*/
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
// 通過識別符號從可變字典中獲取合併任務物件
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
// 通過識別符號從可變字典中移除對應的合併任務物件
[self.mergedTasks removeObjectForKey:URLIdentifier];
return mergedTask;
}
/**
安全的減少當前任務活動數量
*/
- (void)safelyDecrementActiveTaskCount {
// 序列佇列同步執行
dispatch_sync(self.synchronizationQueue, ^{
// 減少當前正在活動的請求數量
if (self.activeRequestCount > 0) {
self.activeRequestCount -= 1;
}
});
}
/**
如果必要的話安全的開啟下一個任務
*/
- (void)safelyStartNextTaskIfNecessary {
// 序列佇列同步執行
dispatch_sync(self.synchronizationQueue, ^{
// 如果當前活動的請求數量沒有到達上限
if ([self isActiveRequestCountBelowMaximumLimit]) {
// 如果當前排隊中有合併任務
while (self.queuedMergedTasks.count > 0) {
// 獲取到排隊中的第一個合併任務物件
AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
// 如果合併任務物件的任務的狀態是暫停,就啟動這個任務
if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[self startMergedTask:mergedTask];
break;
}
}
}
});
}
/**
啟動合併任務物件
*/
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
// 啟動合併任務物件中的任務
[mergedTask.task resume];
// 增加當前活動請求數量
++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;
}
}
/**
獲取到排隊中的第一個合併任務物件
*/
- (AFImageDownloaderMergedTask *)dequeueMergedTask {
AFImageDownloaderMergedTask *mergedTask = nil;
mergedTask = [self.queuedMergedTasks firstObject];
[self.queuedMergedTasks removeObject:mergedTask];
return mergedTask;
}
/**
判斷當前活動請求數量是否達到限制數量
*/
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
複製程式碼
原始碼閱讀系列:AFNetworking
原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization
原始碼閱讀:AFNetworking(三)——AFURLResponseSerialization
原始碼閱讀:AFNetworking(四)——AFSecurityPolicy
原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager
原始碼閱讀:AFNetworking(六)——AFURLSessionManager
原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager
原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache
原始碼閱讀:AFNetworking(九)——AFImageDownloader
原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager
原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking
原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking
原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking
原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking