本篇文章中,笔者将着重介绍AFNetworking的方法调用栈,以及网络图片下载和缓存策略。在此之前,笔者写了一个官方文档翻译版,点击可跳转。
1. 数据下载调用栈
比如,我们要下载一个PDF文件,可采用示例代码如下:
- (void)DownloadPdfAndSave{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/pdf"];
__weak __typeof__(self) weakSelf = self;
//临时配置,需要自己根据接口地址改动!!!!!!!!!!!!!!!!!!!!
self.urlStr = @"http://10.10.100.100/test333.pdf";
[manager GET:_urlStr parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
__strong __typeof__(weakSelf) strongSelf = weakSelf;
strongSelf.isWriten = [responseObject writeToFile:[self pathOfPdf] atomically:YES];
[strongSelf openPdfByAddingSubView];
//[strongSelf.previewController reloadData];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error.userInfo);
}];
}
看到这里,或许可以看manager对象的AFHTTPSessionManager类型更深层定义。
- AFHTTPSessionManager.h
@interface AFHTTPSessionManager : AFURLSessionManager
- AFURLSessionManager
@interface AFURLSessionManager : NSObject
1.1 第一层
AFNetworking/AFHTTPSessionManager.m
- 方法
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
...}
- 源码
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
1.2 第二层
AFNetworking/AFHTTPSessionManager.m
- 方法
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
...}
- 源码
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
1.3 第三层
AFNetworking/AFURLSessionManager.m
- 方法
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler
{
...}
- 源码
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
1.4 第四层
AFNetworking/AFURLSessionManager.m
- 方法
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
...}
- 源码
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
2. 图片缓存与下载管理
2.1 调用示例
- MyTableViewCell.m
- 用法:由UIImageView对象调用如下方法
- (void)setTableCellWithModel:(cellModel *)item{
[_iconImgView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@%@",kBaseImgURL,kMiddleImgURL,item.url]] placeholderImage:[UIImage imageNamed:@"imageFailed"]];
}
2.2 源代码
2.2.1 第一层
- 文件目录:
lib/AFNetworking/UIKit+AFNetworking/UIImageView+AFNetworking.m
-
类名:
- UIImageView+AFNetworking.m
作用:
根据url建立请求代码:
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
2.2.2 第二层
- 文件目录:
lib/AFNetworking/UIKit+AFNetworking/UIImageView+AFNetworking.m
-
类名:
- UIImageView+AFNetworking.m
作用:
先查询缓存,后以此决策下载任务-
逻辑:
- 判断传入的请求中URL是否为空;若为空,则取消下载,imageView显示占位图片,否则,进入下一步判断
- 判断当前的请求是否有task处于活动状态(意思可能用户单位时间内多次发了同一个请求,而在这个请求发送时已经有下载的Task执行了,这个请求将结束)
- 根据请求查看内存缓存中是否目前有当前请求的图片,若有则查看success有无回调,有回调则将图片回调回去,否则,直接设置本ImageView的image
- 若本地内存缓存没有数据,则先将imageView的image设置为占位图片并生成UUID,然后将请求和UUID发送出去; 若成功,则检查UUID是否和之前生成的UUID相等,以防止数据出错,没有问题则根据success是否有回调将图片发送出去;若失败,也检查UUID是否相等,并根据有无failure回调,将错误信息输出
代码:
- (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
{
if ([urlRequest URL] == nil) {
[self cancelImageDownloadTask];
self.image = placeholderImage;
return;
}
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
return;
}
[self cancelImageDownloadTask];
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
id imageCache = downloader.imageCache;
//缓存策略
//Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
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 = [downloader
downloadImageForURLRequest:urlRequest
withReceiptID:downloadID
success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
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;
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
[strongSelf clearActiveDownloadInformation];
}
}];
self.af_activeImageDownloadReceipt = receipt;
}
}
2.2.3 第三层
(1)查询缓存
- 文件目录:
lib/AFNetworking/UIKit+AFNetworking/AFAutoPurgingImageCache.m
类名:
AFAutoPurgingImageCache.m
代码:
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
(2)网络下载
- 文件目录:
lib/AFNetworking/UIKit+AFNetworking/AFImageDownloader.m
类名:
AFImageDownloader.m
逻辑:
- 检查请求的URL是否为空,失败则则返回信息(感觉这一句有点多余,因为调用这个方法之间已经检查了URL是否为空)
- 在Task没有执行时,多个请求可能会同时达到该方法,因此,需要一个字典存储该URL对应的AFImageDownloaderMergedTask,若有,则取出并创建一个AFImageDownloaderResponseHandler放入该自定义Task的一个响应数组中。并将该自定义task的NSURLSessionDataTask赋值给task;若没有,则进行下一步
- 由于对于磁盘缓存作者是使用NSURLCache进行的,因此,根据存储策略进行不同操作以便于下载图片后进行磁盘缓存
4)之后就是从网络下载图片,这也要注意,作者是使用AFN进行异步下载的、会生成UUID和之前一样去检验下载后UUID是否发生改变;若成功,则将先缓存图片,然后将AFImageDownloaderMergedTask中数组所有响应取出并将图片等信息通过回调返回;若失败,也一样进行回调错误信息
- 代码:
- (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) Append the success and failure blocks to a pre-existing request if it already exists
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) Attempt to load the image from the image cache if the cache policy allows it
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) Create the request and set up authentication, validation and response serialization
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;
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
if (error) {
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
});
}
}
} else {
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
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
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) Either start the request or enqueue it depending on the current active request count
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;
}
}