这是第三篇内容,其他内容可以看下方链接:
- 第一部分是请求和响应的对应代码,包括
AFURLRequestSerialization
、AFURLResponseSerialization
- 第二部分是URLSession有关的代码,包括
AFURLSessionManager
和AFHTTPSessionManager
; - 第三部分是辅助的两个类:
AFSecurityPolicy
和AFNetworkReachabilityManager
; - 第四部分是
UIKit+AFNetworking
目录下的有趣内容。
AFAutoPurgingImageCache
该类是用来管理内存中的缓存图片,提供了最大内存容量和首选内存容量,当达到最大容量时,会依次删除(Purging
)最久未使用的缓存图片,直到降到首选内存容量以下。让我们看一下这个功能是如何实现的?
AFImageCache协议
这个协议定义了针对缓存图片的增删查的方法,这些方法都是同步并安全的:
@protocol AFImageCache <NSObject>
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;
- (BOOL)removeImageWithIdentifier:(NSString *)identifier;
- (BOOL)removeAllImages;
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;
@end
这些方法中涉及到入参identifier
,这个值可以作为图片id用来查找图片,一般可以用图片名来表示,当然网络图片也可以用url
来表示,针对这一点,AFN对这个协议进行了扩展。
@protocol AFImageRequestCache <AFImageCache>
// 是否应该缓存,默认实现是YES
- (BOOL)shouldCacheImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
// 增删查方法,支持了传入一个request对象,并以request.URL.absoluteString为图片identifier,同时也可以在默认identifier后拼接AdditionalIdentifier
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
@end
看完协议的定义后,我们继续看一下这个协议的主要实现AFAutoPurgingImageCache
类。
AFAutoPurgingImageCache类
这个类中除了实现AFImageCache
协议相关方法,还增加了内存控制的属性:
// 最大内存
@property (nonatomic, assign) UInt64 memoryCapacity;
// 首选内存
@property (nonatomic, assign) UInt64 preferredMemoryUsageAfterPurge;
// 当前内存用量
@property (nonatomic, assign, readonly) UInt64 memoryUsage;
// init
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;
属性都很好理解,这些属性是如何使用的要看具体的实现了:
@interface AFAutoPurgingImageCache ()
// 用来管理缓存图片的字典
@property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;
@property (nonatomic, assign) UInt64 currentMemoryUsage;
// 保证安全性的队列
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
@end
可以看到这个类内部有一个用来管理缓存图片的字典,这个字典的key
为图片的identifier
,value
为AFCachedImage
对象,对于这个对象,我们看下它的实现:
@interface AFCachedImage : NSObject
// 对象本身持有一个image对象
@property (nonatomic, strong) UIImage *image;
// 对象的唯一标识
@property (nonatomic, copy) NSString *identifier;
// 图片占用总字节数
@property (nonatomic, assign) UInt64 totalBytes;
// 上次获取的时间
@property (nonatomic, strong) NSDate *lastAccessDate;
@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;
}
@end
可以看到AFCachedImage
类相当于对UIImage
进行了包装,添加了一些标识类的属性而已,现在我们回到AFAutoPurgingImageCache
中继续往下看它的方法实现:
- (instancetype)init {
// 默认最大容量100M,首选容量60M
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;
self.cachedImages = [[NSMutableDictionary alloc] init];
// 初始化并发队列
NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
// 收到内存警告时自动删除所有图片!
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllImages) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
/* --- 主体方法,也是AFImageCache协议的实现 --- */
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
// 等读操作结束后执行写操作
dispatch_barrier_async(self.synchronizationQueue, ^{
// 如果当前identifier存在则更新内存大小并替换掉
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
if (previousCachedImage != nil) {
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
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;
for (AFCachedImage *cachedImage in sortedImages) {
// 移除使用时间距今最久的图片
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
// 当前内存图片大小达到preferredMemoryCapacity要求退出循环
if (bytesPurged >= bytesToPurge) {
break;
}
}
self.currentMemoryUsage -= bytesPurged;
}
});
}
- (BOOL)removeImageWithIdentifier:(NSString *)identifier {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
if (cachedImage != nil) {
[self.cachedImages removeObjectForKey:identifier];
self.currentMemoryUsage -= cachedImage.totalBytes;
removed = YES;
}
});
return removed;
}
- (BOOL)removeAllImages {
...
}
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
// 使用指定方法获取图片
image = [cachedImage accessImage];
});
return image;
}
/* ------ */
以上就是初始化方法和AFImageCache
协议方法的实现,其中用到了dispatch_barrier_sync
来保证安全性的方法与AFURLRequestSerialization
中关于header
的处理一致。
再来看一下对于AFImageCache
协议的补充协议AFImageRequestCache
的实现:
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
// 内部还是调用了AFImageCache的增删查方法,只不过在identifier的生成上不同
[self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
...
- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
// 使用url来作为key
NSString *key = request.URL.absoluteString;
if (additionalIdentifier != nil) {
// 如果需要额外标识则进行拼接
key = [key stringByAppendingString:additionalIdentifier];
}
return key;
}
AFImageDownloader
下载图片类,这个类会并行下载图片任务,下载后的图片会缓存到内存中,让我们看一下这个类的声明:
// 这个协议对象上文讲过,默认传入的也是AFAutoPurgingImageCache对象,用来进行图片的缓存
@property (nonatomic, strong, nullable) id <AFImageRequestCache> imageCache;
// 独立于AFN的接口请求的session,专门用来进行图片加载的
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
// 枚举类,标识内部图片加载数组是栈类型还是队列类型
// AFImageDownloadPrioritizationFIFO 队列类型,先进先出
// AFImageDownloadPrioritizationLIFO 栈类型,后进先出
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritization;
// 初始化一个URLCache用来缓存数据
+ (NSURLCache *)defaultURLCache;
// 一些url配置,绑定了URLCache对象
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration;
// 主要方法,其他一些迭代方法就不列举了
// 用来创建图片下载任务,返回一个AFImageDownloadReceipt对象,后面会讲
- (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;
// 通过创建任务时返回的AFImageDownloadReceipt对象来取消下载任务
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt;
声明中涉及到了AFImageDownloadReceipt
对象,这个对象的作用主要就是将创建的task
和一个固定的id
进行绑定,方便根据id
对task
具体操作,算是一个收据对象。看下声明:
// 下载任务
@property (nonatomic, strong) NSURLSessionDataTask *task;
// 通过UUID类生成receiptID,用来标识下载收据的唯一性
@property (nonatomic, strong) NSUUID *receiptID;
实现就不看了,就是进行了属性赋值。接下来我们进入到实现文件看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;
/* 关键属性 */
// 用来按顺序执行下载任务的数组,根据外部的枚举值来判定是栈还是队列,内部存储的是AFImageDownloaderMergedTask类型,下面讲
@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;
// 通过URL来获取对应任务的字典,key为URL字符串,value为AFImageDownloaderMergedTask对象
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;
通过queuedMergedTasks
属性,AFImageDownloader
会从中按顺序取出task
进行下载;通过mergedTasks
对象可以获取每个URL
对应的task
。
可以看到隐式声明中涉及到一个关键类AFImageDownloaderMergedTask
,这个类具体是做什么的呢?
我们先看下它的声明:
@interface AFImageDownloaderMergedTask : NSObject
// URL字符串
@property (nonatomic, strong) NSString *URLIdentifier;
// 生成的UUID,用来确定当前的merge task,注意:和receipt id不是一个东西
@property (nonatomic, strong) NSUUID *identifier;
// 具体的下载任务
@property (nonatomic, strong) NSURLSessionDataTask *task;
// 保存处理回调的数组
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
@end
可以看到这个类做的主要是将URL
、uuid
、task
以及对这个URL
下载的所有回调,很简单。另外还涉及到一个类AFImageDownloaderResponseHandler
,这个类也非常简单,就是保存了成功失败的回调:
@interface AFImageDownloaderResponseHandler : NSObject
// 注意这个uuid就是receipt id,而不是merge task的id
@property (nonatomic, strong) NSUUID *uuid;
@property (nonatomic, copy) void (^successBlock)(NSURLRequest *, NSHTTPURLResponse *, UIImage *);
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest *, NSHTTPURLResponse *, NSError *);
@end
现在我们梳理一下它们的关系:
AFImageDownloadReceipt
在下载任务被创建时会作为收据返回给外部调用者,负责随时取消下载任务。这个类有一个receipt id
,通过它可以拿到AFImageDownloaderResponseHandler
对象来进行回调。而它内部的task
就是AFImageDownloaderMergedTask
的task。AFImageDownloaderMergedTask
作为实际的下载任务,可以通过URL
来匹配。同一个URL
存在多个回调时,会持有多个AFImageDownloaderResponseHandler
。AFImageDownloaderResponseHandler
用来保存receipt id
和成功失败回调。
我们了解了三者关系之后就可以具体看AFImageDownloader
中的实现了:
+ (NSURLCache *)defaultURLCache {
// 默认url cache配置,内存缓存20MB,硬盘缓存150MB,并指定缓存位置
NSUInteger memoryCapacity = 20 * 1024 * 1024;
NSUInteger diskCapacity = 150 * 1024 * 1024;
NSURL *cacheURL = [[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil] URLByAppendingPathComponent:@"com.alamofire.imagedownloader"];
return [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity
diskCapacity:diskCapacity
diskPath:[cacheURL path]];
}
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 基本session配置
configuration.HTTPShouldSetCookies = YES;
configuration.HTTPShouldUsePipelining = NO;
// 缓存策略使用接口协议中的策略
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
configuration.allowsCellularAccess = YES;
configuration.timeoutIntervalForRequest = 60.0;
// 绑定URLCache对象
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
- (instancetype)init {
// 使用默认session configuration
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
return [self initWithSessionConfiguration:defaultConfiguration];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
// 默认使用AFImageResponseSerializer作为响应序列化器
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
// 默认任务数组是队列形式,默认最大并行数4,使用AFAutoPurgingImageCache进行缓存
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]) {
...
// 初始化线程队列
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;
}
以上是初始化相关的方法,再看下这个类的核心方法,创建下载任务:
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success
failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure {
// 默认使用UUID作为每次下载任务的receipt id
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;
// ...
// 第一步:从保存的字典中,获取当前URL的merge task对象,如果存在,只把回调加入到merge task对象的handles数组中
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;
}
// 第二步:根据接口缓存策略,判定是否需要从缓存中获取图片。如果允许读取缓存且缓存中存在图片,则直接回调
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;
}
// 第三步:创建加载图片的任务
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) {
// 第六步:任务成功后异步回调,活跃数量-1,并开始队列中新的任务
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
/// 获取当前URL的merged task
AFImageDownloaderMergedTask *mergedTask = [strongSelf safelyGetMergedTask:URLIdentifier];
/// 确认为当前merged task
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
/// 从字典中移除当前URL对应的merged task对象
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
/// 遍历merged task中的handlers,进行失败或者成功的回调
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];
}
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];
});
}];
// 第四步:用receipt id和成功失败回调初始化handler对象
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
/// 初始化merged task对象,将handler放入其中,使用全局字典保存merged task对象
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc] initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;
// 第五步:如果当前请求中的数量未超过最大限制,则直接开启任务,否则加入queuedMergedTasks队列
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
task = mergedTask.task;
});
// 初始化receipt对象,绑定task和receipt id
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
}
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
// 所有操作均是串行,保证多线程下的安全性
dispatch_sync(self.synchronizationQueue, ^{
// 从receipt对象中获取URL,通过URL获取merged task对象
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
// 从这个merged task的handlers中找到与当前receipt id一直的handler
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
return handler.uuid == imageDownloadReceipt.receiptID;
}];
// 将handler移除并进行失败回调
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];
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
// 如果移除后merge task对象中不存在handler,则取消任务,并移除URL对应的merge task
if (mergedTask.responseHandlers.count == 0) {
[mergedTask.task cancel];
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
现在我们看完了这个类的主要核心方法,其他的安全新增或字典对象、入队入栈的逻辑比较简单,这里就不细说了。我们现在看一下,请求成功后如果开始下一个任务:
- (void)safelyStartNextTaskIfNecessary {
// 同样是串行队列进行操作
dispatch_sync(self.synchronizationQueue, ^{
// 活跃请求不超过最大限制
if ([self isActiveRequestCountBelowMaximumLimit]) {
// 找出数组中处在suspend状态的task进行resume,防止重复开启
while (self.queuedMergedTasks.count > 0) {
// 出队就是取出index 0的task对象
AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[self startMergedTask:mergedTask];
break;
}
}
}
});
}
至此这个类就看完了,梳理下大致逻辑,当遇到需要加载网络图片的情况下时,我们调用downloadImageForURLRequest
方法创建任务,并生成receipt
返回给我们,方便我们随时取消。加载过程会优先根据缓存策略,选择是否去缓存中查找,请求成功后会把当前图片放到内存中方便下次使用。
UIButton+AFNetworking
利用AFImageDownloader
进行UIButton
的image
和backgroundImage
的网络异步下载,我们看一下声明:
// 获取和设置图片下载对象,默认是AFImageDownloader,也可以继承AFImageDownloader修改一些逻辑
+ (AFImageDownloader *)sharedImageDownloader;
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;
/* button image相关 */
// 根据button的state来配置图片
- (void)setImageForState:(UIControlState)state withURL:(NSURL *)url;
// 可以配置placeholder,当获取图片时,如果缓存存在,直接使用缓存图片,否则先用placeholder图片再异步加载
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
// 如果回调为nil,则加载完成后直接调用setImage:forState:进行赋值,否则直接调用外部回调,不做其他操作
- (void)setImageForState:(UIControlState)state
withURLRequest:(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;
// 取消当前state的图片下载任务
- (void)cancelImageDownloadTaskForState:(UIControlState)state;
/* button background image 相关,与image一致,不作讲解了 */
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url;
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
- (void)setBackgroundImageForState:(UIControlState)state
withURLRequest:(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)cancelBackgroundImageDownloadTaskForState:(UIControlState)state;
方法都很好理解,我们看下类别内部的实现,首先实现文件里面声明了一个额外的类别,主要就是使用runtime
提供了收据对象的getter
和setter
方法:
@implementation UIButton (_AFNetworking)
static char AFImageDownloadReceiptNormal;
static char AFImageDownloadReceiptHighlighted;
static char AFImageDownloadReceiptSelected;
static char AFImageDownloadReceiptDisabled;
static const char * af_imageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFImageDownloadReceiptNormal;
}
}
- (AFImageDownloadReceipt *)af_imageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_imageDownloadReceiptKeyForState(state));
}
- (void)af_setImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state {
objc_setAssociatedObject(self, af_imageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
...
@end
通过上面的方法,为UIButton
的四种state
都配置了内存空间来保存对应的receipt
对象,为了可以保证每个state
只会有一个加载任务。
然后看一下真正的类别的实现:
@implementation UIButton (AFNetworking)
// runtime增加sharedImageDownloader属性,默认是AFImageDownloader单例
+ (AFImageDownloader *)sharedImageDownloader {
return objc_getAssociatedObject([UIButton class], @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
}
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
objc_setAssociatedObject([UIButton class], @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
...
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage {
// 将url包装成request,header中配置接收图片类型数据
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setImageForState:(UIControlState)state
withURLRequest:(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 {
// 如果当前state下存在请求中的图片则return
if ([self isActiveTaskURLEqualToURLRequest:urlRequest forState:state]) {
return;
}
// 取消当前state下可能已存在的下载任务
[self cancelImageDownloadTaskForState:state];
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
// 从download里面拿到图片缓存对象
id <AFImageRequestCache> imageCache = downloader.imageCache;
// 从缓存中获取当前request对应的图片,有则直接回调,没有则设置placeholder再去加载
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
if (cachedImage) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
[self setImage:cachedImage forState:state];
}
[self af_setImageDownloadReceipt:nil forState:state];
} else {
// 优先设置placeholder
if (placeholderImage) {
[self setImage:placeholderImage forState:state];
}
__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_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
// 如果存在外部的回调就直接通知外部,否则直接设置当前状态的图片
if (success) {
success(request, response, responseObject);
} else if (responseObject) {
[strongSelf setImage:responseObject forState:state];
}
[strongSelf af_setImageDownloadReceipt:nil forState:state];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[strongSelf af_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
[strongSelf af_setImageDownloadReceipt:nil forState:state];
}
}];
// 将当前的state和收据对象绑定
[self af_setImageDownloadReceipt:receipt forState:state];
}
}
- (void)cancelImageDownloadTaskForState:(UIControlState)state {
// 获取当前state对应的收据对象来取消下载任务
AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state];
if (receipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:receipt];
[self af_setImageDownloadReceipt:nil forState:state];
}
}
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest forState:(UIControlState)state {
// 如果已经存在收据且url一致,表明正在下载对应图片
AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state];
return [receipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
这个类别很简单,内存中存在了图片则直接加载,不存在会去重新请求,而请求的时候也会验证时候有缓存。
UIImageView+AFNetworking
的逻辑和这个基本一致,我就不再赘述了。
AFNetworkActivityIndicatorManager
这个类是用来自动控制状态栏时间
右侧的小菊花显示的,按照苹果的说法,这种UI设计会更符合人机交互科学性。功能实现并不复杂,利用的是AFURLSessionManager
类中讲到的通知机制:
- 当
task
完成后,TaskDelegate
会发送完成通知。 - 通过
runtime
重新了task
对象的resume
方法,发送开启通知。 - 通过
runtime
重新了task
对象的suspend
方法,发送开启通知。
让我们具体看一下它的声明文件:
// 总开关,默认是NO,不展示小菊花
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
// 只读属性,小菊花是否显示
@property (readonly, nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
// 小菊花出现的延迟,按照苹果人机交互上说的,如果请求时间过于短,就不需要展示小菊花,这样更合理。默认值是1秒
@property (nonatomic, assign) NSTimeInterval activationDelay;
// 小菊花消失的延迟,默认是0.17秒
@property (nonatomic, assign) NSTimeInterval completionDelay;
// 手动增加网络请求计数,内部有根据通知的默认实现,正常来说不需要手动调用增减方法
- (void)incrementActivityCount;
// 手动减少网络请求计数
- (void)decrementActivityCount;
// 设置触发网络后的回调,默认是使用系统的小菊花样式,也可以设置自有的大菊花样式...
- (void)setNetworkingActivityActionWithBlock:(nullable void (^)(BOOL networkActivityIndicatorVisible))block;
声明上看非常简单吧,而且AFN
为我们做了绝大多数事情,下面我们就看下它到底是如何做到的:
// 用来控制当前指示器的状态
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
AFNetworkActivityManagerStateNotActive, // 不活跃
AFNetworkActivityManagerStateDelayingStart, // 延迟显示中
AFNetworkActivityManagerStateActive, // 活跃中
AFNetworkActivityManagerStateDelayingEnd // 延迟消失中
};
// notification中的object对象是NSURLSessionTask对象
static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notification) {
if ([[notification object] respondsToSelector:@selector(originalRequest)]) {
return [(NSURLSessionTask *)[notification object] originalRequest];
} else {
return nil;
}
}
@interface AFNetworkActivityIndicatorManager ()
// 当前活跃的网络请求数量计数
@property (readwrite, nonatomic, assign) NSInteger activityCount;
// 延迟显示的定时器
@property (readwrite, nonatomic, strong) NSTimer *activationDelayTimer;
// 延迟消失的定时器
@property (readwrite, nonatomic, strong) NSTimer *completionDelayTimer;
// 小菊花是否显示
@property (readonly, nonatomic, getter = isNetworkActivityOccurring) BOOL networkActivityOccurring;
// 自定义的触发回调
@property (nonatomic, copy) AFNetworkActivityActionBlock networkActivityActionBlock;
// 当前的state
@property (nonatomic, assign) AFNetworkActivityManagerState currentState;
@property (nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
// 当网络请求完成或者开启时,更新活动状态
- (void)updateCurrentStateForNetworkActivityChange;
@end
再来看下具体的实现:
- (instancetype)init {
...
self.currentState = AFNetworkActivityManagerStateNotActive;
// 对AFURLSessionManager中的三个通知进行监听
[[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;
}
// 触发后通知后,自动增加或者减少activityCount计数
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self incrementActivityCount];
}
}
// 增加计数并刷新状态
- (void)incrementActivityCount {
// 递归锁保护一下安全性
@synchronized(self) {
self.activityCount++;
}
// 递增之后根据activityCount更改状态
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
- (void)updateCurrentStateForNetworkActivityChange {
// 总开关控制
if (self.enabled) {
switch (self.currentState) {
case AFNetworkActivityManagerStateNotActive:
// 存在活跃的请求,将状态设为DelayingStart
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingStart];
}
break;
case AFNetworkActivityManagerStateDelayingStart:
// 不需要修改,因为定时器的回调方法里面已经改过了
break;
case AFNetworkActivityManagerStateActive:
// 不存在活跃请求,将状态设为DelayingEnd
if (!self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingEnd];
}
break;
case AFNetworkActivityManagerStateDelayingEnd:
// 存在活跃请求,设置当前状态为Active
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
}
break;
}
}
}
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
@synchronized(self) {
if (_currentState != currentState) {
_currentState = currentState;
// 根据不同的状态,进行不同的操作
switch (currentState) {
case AFNetworkActivityManagerStateNotActive:
// 非活跃状态,取消定时器,小菊花不可见
[self cancelActivationDelayTimer];
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:NO];
break;
case AFNetworkActivityManagerStateDelayingStart:
// Delay start状态启动展示定时器
[self startActivationDelayTimer];
break;
case AFNetworkActivityManagerStateActive:
// 小菊花展示状态,取消消失定时器,设置小菊花显示
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:YES];
break;
case AFNetworkActivityManagerStateDelayingEnd:
// Delay end状态,开启消失定时器
[self startCompletionDelayTimer];
break;
}
}
}
}
// 两个定时器的回调如下
// 展示定时器
- (void)startActivationDelayTimer {
self.activationDelayTimer = [NSTimer timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)activationDelayTimerFired {
if (self.networkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
} else {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
// 消失定时器
- (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)completionDelayTimerFired {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
该类结束,我们继续往下看,加油。
UIActivityIndicatorView+AFNetworking
该类是UIActivityIndicatorView
的一个类别,负责根据具体的task
对象来自动展示大菊花
,这个类比较简单,跟AFNetworkActivityIndicatorManager
一样,都是利用了AFURLSessionManager
的通知来进行的。它的声明非常简单,只有一个方法:
// 利用这个方法来将task对象的状态绑定到大菊花上,开启task后显示大菊花,task结束或挂起后大菊花消失
// 如果传入nil,则取消所有任务的自动大菊花功能
- (void)setAnimatingWithStateOfTask:(nullable NSURLSessionTask *)task;
声明简单,实现也一样简单,通过runtime
将本身绑定到一个notification observer
对象上进行操作:
@implementation UIActivityIndicatorView (AFNetworking)
- (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver {
// 使用runtime添加属性方法
AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
// 将状态传给observer类处理
[[self af_notificationObserver] setAnimatingWithStateOfTask:task];
}
@end
这个notification observer
主要处理了task
对象的通知回调逻辑,我们看一下声明:
@interface AFActivityIndicatorViewNotificationObserver : NSObject
// 弱引用
@property (readonly, nonatomic, weak) UIActivityIndicatorView *activityIndicatorView;
// 初始化保存外部的indicator
- (instancetype)initWithActivityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView;
// 接收外部的task变化
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task;
@end
实现方法:
@implementation AFActivityIndicatorViewNotificationObserver
...
// 核心方法
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
// 利用通知来处理task的状态变化
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
// 每次设置都会先移除所有的通知,保证每次只能被一个task绑定,防止不同task交互影响闪动
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
if (task) {
if (task.state != NSURLSessionTaskStateCompleted) {
UIActivityIndicatorView *activityIndicatorView = self.activityIndicatorView;
// 根据当前状态来设置大菊花
if (task.state == NSURLSessionTaskStateRunning) {
[activityIndicatorView startAnimating];
} else {
[activityIndicatorView stopAnimating];
}
// 添加开启、完成、挂起的通知
[notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingTaskDidResumeNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidCompleteNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidSuspendNotification object:task];
}
}
}
- (void)af_startAnimating {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicatorView startAnimating];
});
}
- (void)af_stopAnimating {
...
}
@end
这个类非常的简单,不过我们可以学习这种代码设计方法,将复杂的功能交给指定的delegate
或者observer
处理,减少代码的耦合性。
UIRefreshControl+AFNetworking
刷新的菊花也是用同样的方式实现自动刷新的,感兴趣可以自己看一下,这里就不重复展开了。
UIProgressView+AFNetworking
这个类主要是使用KVO方式来让UIProgressView
根据上传或下载任务的进度自动进行更新。我们看一下主要代码:
@interface UIProgressView (AFNetworking)
// 绑定task对象,根据task进行自动更新
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated;
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated;
@end
@implementation UIProgressView (AFNetworking)
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated {
if (task.state == NSURLSessionTaskStateCompleted) {
return;
}
// 为task对象添加观察,context指定上下文,防止跑错片场
[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
// 利用runtime给UIProgressView添加一个记录*是否动画*的属性
[self af_setUploadProgressAnimated:animated];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(__unused NSDictionary *)change
context:(void *)context {
if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
if ([object countOfBytesExpectedToSend] > 0) {
// 主线程更新上传进度
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
});
}
}
...
if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
// 任务完成时尝试移除观察者
if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
@try {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];
if (context == AFTaskCountOfBytesSentContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
}
...
}
@catch (NSException * __unused exception) {}
}
}
}
}
@end
下载的逻辑和上传逻辑一模一样,均是通过KVO实现,大致了解就好,没啥可讲的。
WKWebView+AFNetworking
这个类别的作用是代替WebView
内部的加载在线网页逻辑,转而使用AFNetworking
直接请求,再将请求下来的数据使用WebView
进行加载,这样做的好处主要是可以通过AFN
更精确地把控不同阶段的回调。
不过这类在我使用来看,存在问题,所以暂时就不讲解了,等之后找到解决办法再贴出来。
有兴趣的可以去看一下:issue地址
结语
这个系列写了很久,因为要做日常开发,所以都是断断续续地写的,以前看源码都是遇到问题去找解决方案,这是第一次从头读到尾,每一行都进行理解之后才继续进行的,个人来说受益匪浅,也希望我这里的一些简单分享,能够帮助到你们,谢谢。