AFNetworking 4.x 最全的源码说明书(四)

702 阅读21分钟

这是第三篇内容,其他内容可以看下方链接:

  • 第一部分是请求和响应的对应代码,包括AFURLRequestSerializationAFURLResponseSerialization
  • 第二部分是URLSession有关的代码,包括AFURLSessionManagerAFHTTPSessionManager
  • 第三部分是辅助的两个类:AFSecurityPolicyAFNetworkReachabilityManager
  • 第四部分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为图片的identifiervalueAFCachedImage 对象,对于这个对象,我们看下它的实现:

@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进行绑定,方便根据idtask具体操作,算是一个收据对象。看下声明:

// 下载任务
@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

可以看到这个类做的主要是将URLuuid 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 进行UIButtonimagebackgroundImage的网络异步下载,我们看一下声明:

// 获取和设置图片下载对象,默认是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提供了收据对象的gettersetter方法:

@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地址

结语

这个系列写了很久,因为要做日常开发,所以都是断断续续地写的,以前看源码都是遇到问题去找解决方案,这是第一次从头读到尾,每一行都进行理解之后才继续进行的,个人来说受益匪浅,也希望我这里的一些简单分享,能够帮助到你们,谢谢。