从AFNetworking源码分析到应用全解 一、引言 AFNetworking是iOS/OS开发中常用的一个第三方网络库,可以说它是目前最流行的网络库,但其代码结构其实并不复杂,也可以说非常简洁优美。在AFNetworking中,大量使用的线程安全的开发技巧,读此源码也是一次很好的多线程学习机会。本篇博客从主要结构和网络请求的主流程进行分享,解析了AFNetworking的设计思路与工作原理,后面还有其中提供的UI扩展包的接口应用总结。
每次读优秀的代码都是一次深刻的学习,每一次模仿,都是创造的开始!
二、核心源码分析 平时我们在使用AFNetworking框架时,大多只使用其中的请求管理功能。其实,这个有名的框架中还提供了许多其他的工具,除了可以方便的进行网络安全验证,请求数据与回执数据的序列化,网络状态茶台等基础应用外,还提供了UIKit工具包,其中提供有常用组件的扩展,图片下载器和缓存器等。
对于AFNetworking框架的核心,无非AFURLSesstionManager类,这个类是基于系统的NSURLSesstion回话类进行的管理者包装,下图是AF框架一个整体的结构。
把握这个结构,我们再来学习AF框架将变得十分容易上手,打开AFURLSesstionManager类,你会发现它有1200多行代码,但是AFURLSesstionManager类真正的实现确实从500多行开始的,之前的代码是内部的代理处理类,就像在MVVM模式中,我们总是喜欢将控制器的逻辑放入View-Model中一样,AFURLSesstionManager实例也会将通知,回调等操作交给这个代理实例处理。
1.AFURLSesstionManager 首先先来看AFURLSesstionManager类的初始化方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 - (instancetype )initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (!self ) { return nil ; } if (!configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } self .sessionConfiguration = configuration; self .operationQueue = [[NSOperationQueue alloc] init]; self .operationQueue.maxConcurrentOperationCount = 1 ; self .session = [NSURLSession sessionWithConfiguration:self .sessionConfiguration delegate:self delegateQueue:self .operationQueue]; self .responseSerializer = [AFJSONResponseSerializer serializer]; self .securityPolicy = [AFSecurityPolicy defaultPolicy]; #if !TARGET_OS_WATCH self .reachabilityManager = [AFNetworkReachabilityManager sharedManager]; #endif self .mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; self .lock = [[NSLock alloc] init]; self .lock.name = AFURLSessionManagerLockName; [self .session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { for (NSURLSessionDataTask *task in dataTasks) { [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil ]; } for (NSURLSessionUploadTask *uploadTask in uploadTasks) { [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil ]; } for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil ]; } }]; return self ; }
用AFURLSesstionManager创建请求任务有三类,当然也对应NSURLSesstion中的数据请求任务,上传任务和下载任务。我们以数据请求任务为例,核心方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (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] init]; delegate.manager = self ; delegate.completionHandler = completionHandler; dataTask.taskDescription = self .taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask]; delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; }
需要注意,原生的NSURLSesstionTask的代理其实依然是AFURLSesstionManager类自身,这里Manager作为中介来进行方法的传递(它也会自己处理一些回调,与开发者相关的回调才会转给内部代理处理)。
上面的流程就是AFURLSesstionManager创建的任务的主流程了,需要注意,它只创建出任务并不会执行,需要开发者手动调用resume才能激活任务。下面的流程就是系统的NSURLSesstionTaskDelagate相关的回调了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler { NSURLRequest *redirectRequest = request; if (self .taskWillPerformHTTPRedirection) { redirectRequest = self .taskWillPerformHTTPRedirection(session, task, response, request); } if (completionHandler) { completionHandler(redirectRequest); } } - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling ; __block NSURLCredential *credential = nil ; if (self .taskDidReceiveAuthenticationChallenge) { disposition = self .taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust ]) { if ([self .securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential ; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge ; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling ; } } if (completionHandler) { completionHandler(disposition, credential); } } - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler { NSInputStream *inputStream = nil ; if (self .taskNeedNewBodyStream) { inputStream = self .taskNeedNewBodyStream(session, task); } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol (NSCopying )]) { inputStream = [task.originalRequest.HTTPBodyStream copy ]; } if (completionHandler) { completionHandler(inputStream); } } - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { int64_t totalUnitCount = totalBytesExpectedToSend; if (totalUnitCount == NSURLSessionTransferSizeUnknown ) { NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length" ]; if (contentLength) { totalUnitCount = (int64_t) [contentLength longLongValue]; } } if (self .taskDidSendBodyData) { self .taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); } } - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; if (delegate) { [delegate URLSession:session task:task didCompleteWithError:error]; [self removeDelegateForTask:task]; } if (self .taskDidComplete) { self .taskDidComplete(session, task, error); } } - (void )URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow ; if (self .dataTaskDidReceiveResponse) { disposition = self .dataTaskDidReceiveResponse(session, dataTask, response); } if (completionHandler) { completionHandler(disposition); } } - (void )URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; if (delegate) { [self removeDelegateForTask:dataTask]; [self setDelegate:delegate forTask:downloadTask]; } if (self .dataTaskDidBecomeDownloadTask) { self .dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); } } - (void )URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; [delegate URLSession:session dataTask:dataTask didReceiveData:data]; if (self .dataTaskDidReceiveData) { self .dataTaskDidReceiveData(session, dataTask, data); } } - (void )URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { NSCachedURLResponse *cachedResponse = proposedResponse; if (self .dataTaskWillCacheResponse) { cachedResponse = self .dataTaskWillCacheResponse(session, dataTask, proposedResponse); } if (completionHandler) { completionHandler(cachedResponse); } } - (void )URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; if (self .downloadTaskDidFinishDownloading) { NSURL *fileURL = self .downloadTaskDidFinishDownloading(session, downloadTask, location); if (fileURL) { delegate.downloadFileURL = fileURL; NSError *error = nil ; [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]; if (error) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo]; } return ; } } if (delegate) { [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; } }
到此,AFURLSesstionManager类的任务基本完成,头文件中的接口更多提供了上述回调的设置还有些通知的发送。下面我们要来看下内部代理AFURLSesstionManagerTaskDelegate类的作用,需要注意AFURLSesstionManagerTaskDelegate是一个类,并不是协议,其除了处理上面介绍的Manager转发过来的回到外,还会进行请求进度的控制。其配置方法和一些监听这里不再过多介绍,主要来看其对Manager转发过来的回到的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 - (void )URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self .mutableData appendData:data]; } - (void )URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" __strong AFURLSessionManager *manager = self .manager; __block id responseObject = nil ; __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer; NSData *data = nil ; if (self .mutableData) { data = [self .mutableData copy ]; self .mutableData = nil ; } if (self .downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self .downloadFileURL; } else if (data) { userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data; } if (error) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = error; dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self .completionHandler) { self .completionHandler(task.response, responseObject, error); } dispatch_async (dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); } else { dispatch_async (url_session_manager_processing_queue(), ^{ NSError *serializationError = nil ; responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError]; if (self .downloadFileURL) { responseObject = self .downloadFileURL; } if (responseObject) { userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject; } if (serializationError) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError; } dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self .completionHandler) { self .completionHandler(task.response, responseObject, serializationError); } dispatch_async (dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); }); } #pragma clang diagnostic pop } - (void )URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSError *fileManagerError = nil ; self .downloadFileURL = nil ; if (self .downloadTaskDidFinishDownloading) { self .downloadFileURL = self .downloadTaskDidFinishDownloading(session, downloadTask, location); if (self .downloadFileURL) { [[NSFileManager defaultManager] moveItemAtURL:location toURL:self .downloadFileURL error:&fileManagerError]; if (fileManagerError) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } } } }
2.AFHTTPSesstionManager 了解了AFURLSesstionManager,学习AFHTTPSesstionManager将十分轻松,这个类只是前者面向应用的一层封装。我们可以先从它的接口看起,这也是开发者最熟悉和常用的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 @interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding , NSCopying @property (readonly , nonatomic , strong , nullable ) NSURL *baseURL;@property (nonatomic , strong ) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;@property (nonatomic , strong ) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;+ (instancetype )manager; - (instancetype )initWithBaseURL:(nullable NSURL *)url; - (instancetype )initWithBaseURL:(nullable NSURL *)url sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER ; - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id )parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id )parameters progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString parameters:(nullable id )parameters success:(nullable void (^)(NSURLSessionDataTask *task))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id )parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id )parameters progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id )parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id )parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(nullable id )parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(nullable id )parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(nullable id )parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
上面所有的请求,一旦创建就会自动resume,开发则不需要额外开启。
3.请求序列化AFURLRequestSerialization AFURLRequestSerialization是一个协议,它的作用其实就是来将请求进行配置,其中只定义了一个接口:
1 2 3 4 5 6 7 @protocol AFURLRequestSerialization <NSObject , NSSecureCoding , NSCopying >- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id )parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW ; @end
实现这个接口的类主要是AFHTTPRequestSerizlizaer类,其还有两个子类,分别为AFJSONRequestSerizlizaer和AFPropertyListRequestSerializer类。
在使用AFNetworking进行网络请求时,如果你有过抓包,你一定会发现,在发送的普通HTTP请求的HEAD中默认包含了许多信息,其实这些都是AFHTTPRequestSerizlizaer类做的,他默认会向请求头中添加Accept-Language,User-Agent以及其他可配置的头部信息(缓存,Cookie,超时时间,用户名密码等)。在进行请求参数配置的时候,AFHTTPRequestSerizlizaer会根据请求方法来选择配置到url后面或者添加到请求body中(HEAD,DELETE,GET会追加URL,其他添加body)。AFJSONRequestSerizlizaer的作用与AFHTTPRequestSerizlizaer一致,不同的是会将请求头中的Content-Type设置为application/json并且将参数格式化成JSON数据放置在请求体中,AFPropertyListRequestSerializer类则是将Content-Type设置为application/x-plist,并且将参数格式化成Plist数据放入请求体。
4.回执数据序列化AFURLResponseSerialization AFNetworking进行网络请求有一个十分方便的地方在于它可以直接将返回数据进行解析。其中AFHTTPResponseSerializer是最基础的解析类,它只会根据返回头信息来校验返回数据的有效性,整理后直接将原数据返回。AFJSONResponseSerializer类用来解析返回数据为JSON数据的回执,用这个类进行解析时,返回头信息中的MIMEType必须为application/json,text/json或text/javascript。AFXMLParserResponseSerializer类用来解析XML数据,其会返回一个XML解析器,使用它时,返回头信息中的MIMEType必须为application/xml或text/xml。AFXMLDocumentResponseSerializer类将返回数据解析成XML文档。AFPropertyListResponseSerializer用来将返回数据解析成Plist数据。AFImageResponseSerializer类用来将返回数据解析成UIImage图片,其支持的MIMEType类型为image/tiff,image/jpeg,image/gif,image/png,image/ico,image/x-icon,image/bmp,image/x-bmp,image/x-xbitmap,image/x-win-bitmap。
除了上面列出的这些类外,还有一个AFCompoundResponseSerializer类,这个类示例中可以配置多个ResponseSerializer实例,解析的时候会进行遍历尝试找到可以解析的模式,这种模式也叫混合解析模式。
三、UI工具包源码分析 1.AFAutoPurgingImageCache图片缓存源码分析 AFAutoPurgingImageCache类是AF框架中提供的图片缓存器,需要注意,它并不是一个持久化的缓存工具,只做临时性的缓存。其中封装了自动清缓存和按时间命中的逻辑。
每一个AFAutoPurgingImageCache类实例中都有一个缓存池,缓存池有两个临界值,最大容量与期望容量。当实际使用的内存超过最大容量时,缓存池会自动清理到期望容量。在缓存池中,存放的实际上是AFCacheImage对象,这个内部类对UIImage进行了包装,如下:
1 2 3 4 5 6 7 8 9 10 11 12 @interface AFCachedImage : NSObject @property (nonatomic , strong ) UIImage *image;@property (nonatomic , strong ) NSString *identifier;@property (nonatomic , assign ) UInt64 totalBytes;@property (nonatomic , strong ) NSDate *lastAccessDate;@property (nonatomic , assign ) UInt64 currentMemoryUsage;@end
清缓存的逻辑十分简单,这里就不做代码解析,流程是每次进行图片缓存时,判断是否超出缓存池最大容量,如果超出,将AFCacheImage对象按照lastAccessDate属性进行排序后进行按顺序删除直到到达期望容量。当收到系统的内存警告时,也会唤起清除内存操作。
2.AFImageDownloader图片下载器源码解析 AFImageDownloader类专门用来下载图片,其但单独的线程的进行图片的下载处理,并且内置了多任务挂起等待和图片数据缓存的特性。AFImageDownloder类的核心有两个线程:
1 2 @property (nonatomic , strong ) dispatch_queue_t synchronizationQueue;@property (nonatomic , strong ) dispatch_queue_t responseQueue;
其中synchronizationQueue是一个同步线程,用来创建与开始下载任务,也可以理解这个串行线程为这个下载器类的主要代码执行所在的线程,responseQueue是一个并行线程,其用来当请求完成后处理数据。默认情况下,下载器可以同时下载4张图片,如果图片的请求大于4,多出的请求会被暂时挂起,等待其他请求完成在进行激活。其核心流程如下图所示:
如上图所示,AFImageDownloader类中有大量的操作任务池和修改激活任务数的操作,为了保证数据的安全,这也就是为何AFImageDownloader的主题操作要在其自建的串行线程中执行。核心方法代码解析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 - (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 ; } 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 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]; }); }]; 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; 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 ; } }
UIImageView+AFNetworking类别与UIButton+AFNetworking类别是AF中提供了两个加载网络图片的工具类别。这两个类别都为其实例对象关联了一个图片下载器,开发者可以自定义这个下载器也可以使用默认提供的,例如:
1 2 3 4 5 6 7 8 9 10 11 + (AFImageDownloader *)sharedImageDownloader { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self , @selector (sharedImageDownloader)) ?: [AFImageDownloader defaultInstance]; #pragma clang diagnostic pop } + (void )setSharedImageDownloader:(AFImageDownloader *)imageDownloader { objc_setAssociatedObject(self , @selector (sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
虽然头文件中提供了多个设置图片的接口方法,但实际核心方法只有一个,其他方法都是基于这个核心方法时候封装的遍历方法,以UIImageView类别为例,UIButton类别也类似,如图:
进行网络图片设置的过程如下图:
核心方法代码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 - (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 <AFImageRequestCache> imageCache = downloader.imageCache; 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; } }
4.AFNetworkActivityIndicatorManager类设计解析 AFNetworkActivityIndicatorManager类用来管理系统在状态栏上的指示器显示或隐藏。系统状态栏上的指示器提供的接口非常简单,只有一个:
1 [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES ];
上面方法的参数决定指示器是否显示。AFNetworkActivityIndicatorManager从两个方向来管理这个指示器,可以开发者手动设置,同时他也会对所有AFNetworking发出的请求状态进行监听,来自动适应决定是否显示指示器。
以前我在设计全局Loading时,通常直接为他暴漏显隐两个接口,当处理多个并行请求的时候就很尴尬了,因为你无法保证Loading在最后完成的请求结束后再隐藏。 AFNetworkActivityIndicatorManager采用了触发器的设计模式(其实有些像引用计数),请求来对触发器进行加或减的操作,触发器决定是否触发显示指示器。后面的应用解析中会有具体的接口解释。
四、应用全解 1.检测网络状态 在AFNetworking中提供了管理类AFNetworkReachabilityManager类,这个类专门用于网络状态的检测。其代码量很少,接口设计也十分简单。AFNetworkReachabilityManager类解析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 + (instancetype )sharedManager; + (instancetype )manager; + (instancetype )managerForDomain:(NSString *)domain; + (instancetype )managerForAddress:(const void *)address; - (instancetype )initWithReachability:(SCNetworkReachabilityRef )reachability; @property (readonly , nonatomic , assign ) AFNetworkReachabilityStatus networkReachabilityStatus;@property (readonly , nonatomic , assign , getter = isReachable) BOOL reachable;@property (readonly , nonatomic , assign , getter = isReachableViaWWAN) BOOL reachableViaWWAN;@property (readonly , nonatomic , assign , getter = isReachableViaWiFi) BOOL reachableViaWiFi;- (void )startMonitoring; - (void )stopMonitoring; - (void )setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block; - (NSString *)localizedNetworkReachabilityStatusString; FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification; FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
这个类别是AFNetworking中UI部分所提供了一个工具,用来创建远程图片按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 + (void )setSharedImageDownloader:(AFImageDownloader *)imageDownloader; + (AFImageDownloader *)sharedImageDownloader; - (void )setImageForState:(UIControlState )state withURL:(NSURL *)url; - (void )setImageForState:(UIControlState )state withURL:(NSURL *)url placeholderImage:(nullable UIImage *)placeholderImage; - (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; - (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 )cancelImageDownloadTaskForState:(UIControlState )state; - (void )cancelBackgroundImageDownloadTaskForState:(UIControlState )state;
3.UIImageView+AFNetworking类别 这个类别是AFNetworking框架提供了UIImageView加载异步图片的方案(更多时候可能会用SDWebImage)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + (void )setSharedImageDownloader:(AFImageDownloader *)imageDownloader; + (AFImageDownloader *)sharedImageDownloader; - (void )setImageWithURL:(NSURL *)url; - (void )setImageWithURL:(NSURL *)url placeholderImage:(nullable UIImage *)placeholderImage; - (void )setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; - (void )cancelImageDownloadTask;
4.状态栏的网络状态指示器 AFNetworking的UI工具包中提供了AFNetworkActivityIndicatorManager类,这个管理类用来对iOS设备状态栏上活动指示器的显示隐藏进行管理。其提供的接口十分简单,解析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @property (nonatomic , assign , getter = isEnabled) BOOL enabled;@property (readonly , nonatomic , assign , getter =isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;@property (nonatomic , assign ) NSTimeInterval activationDelay;@property (nonatomic , assign ) NSTimeInterval completionDelay;+ (instancetype )sharedManager; - (void )incrementActivityCount; - (void )decrementActivityCount; - (void )setNetworkingActivityActionWithBlock:(nullable void (^)(BOOL networkActivityIndicatorVisible))block;
5.UIActivityIndicatorView+AFNetworking与UIRefreshControl+AFNetworking类别 这两个类别可以将一个请求任务绑定到活动指示器或原生刷新组件上,任务的完成与否来决定控件是否展示动画。
1 2 3 4 5 6 7 - (void )setAnimatingWithStateOfTask:(nullable NSURLSessionTask *)task; - (void )setRefreshingWithStateOfTask:(NSURLSessionTask *)task;
6.UIProgressView+AFNetworking类别 这个类别可以将上传和下载任务绑定到进度条组件上。
1 2 3 4 5 6 - (void )setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task animated:(BOOL )animated; - (void )setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task animated:(BOOL )animated;
7.UIWebView+AFNetworking类别 这个类别是对UIWebView的一种扩展(由于WebKit,这个类别很少会用到了),其主要作用是将WebView的直接加载改为先下载本地数据,然后进行本地数据的加载,并可以提供一个进度。
1 2 3 4 5 6 7 8 9 10 11 12 - (void )loadRequest:(NSURLRequest *)request progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success failure:(nullable void (^)(NSError *error))failure; - (void )loadRequest:(NSURLRequest *)request MIMEType:(nullable NSString *)MIMEType textEncodingName:(nullable NSString *)textEncodingName progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(nullable void (^)(NSError *error))failure;
8.图片下载器AFImageDownloader的应用 AFImageDownloader是AF框架中提供的图片下载管理类。其内部封装了一个AFImageDownloadReceipt回执类,这个类实例用来进行正在下载图片任务的取消。AFImageDownloader解析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @property (nonatomic , strong , nullable ) id <AFImageRequestCache> imageCache;@property (nonatomic , strong ) AFHTTPSessionManager *sessionManager;@property (nonatomic , assign ) AFImageDownloadPrioritization downloadPrioritizaton;+ (instancetype )defaultInstance; + (NSURLCache *)defaultURLCache; - (instancetype )initWithSessionManager:(AFHTTPSessionManager *)sessionManager downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization maximumActiveDownloads:(NSInteger )maximumActiveDownloads imageCache:(nullable id <AFImageRequestCache>)imageCache; - (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; - (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;
9.图片缓存器应用 AF框架中UIKit工具包里的ADAutoPurgingImageCache类用来进行图片缓存,其还封装了自动清缓存的逻辑,缓存的命中则是完全按照使用时间先后为标准。类接口解析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @protocol AFImageCache <NSObject >- (void )addImage:(UIImage *)image withIdentifier:(NSString *)identifier; - (BOOL )removeImageWithIdentifier:(NSString *)identifier; - (BOOL )removeAllImages; - (nullable UIImage *)imageWithIdentifier:(NSString *)identifier; @end @protocol AFImageRequestCache <AFImageCache >- (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 @interface AFAutoPurgingImageCache : NSObject <AFImageRequestCache >@property (nonatomic , assign ) UInt64 memoryCapacity;@property (nonatomic , assign ) UInt64 preferredMemoryUsageAfterPurge;@property (nonatomic , assign , readonly ) UInt64 memoryUsage;- (instancetype )init; - (instancetype )initWithMemoryCapacity:(UInt64 )memoryCapacity preferredMemoryCapacity:(UInt64 )preferredMemoryCapacity; @end
每次读优秀的代码都是一次深刻的学习,每一次模仿,都是创造的开始!
——QQ 316045346 欢迎交流