关于AFNetworking的简单总结。
终于有时间把AFNetworking源码看完了,做个记录。
一、AFHTTPSessionManager 1,基本使用 引入AFNetworking框架后,我们使用AFHTTPSessionManager可以完成网络请求相关的工作。
AFHTTPSessionManager的头文件如下:
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 @interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding , NSCopying >@property (readonly , nonatomic , strong , nullable ) NSURL *baseURL;@property (nonatomic , strong ) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;@property (nonatomic , strong ) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;@property (nonatomic , strong ) AFSecurityPolicy *securityPolicy;+ (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 headers:(nullable NSDictionary <NSString *, NSString *> *)headers 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 headers:(nullable NSDictionary <NSString *, NSString *> *)headers success:(nullable void (^)(NSURLSessionDataTask *task))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id )parameters headers:(nullable NSDictionary <NSString *, NSString *> *)headers 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 headers:(nullable NSDictionary <NSString *, NSString *> *)headers 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 headers:(nullable NSDictionary <NSString *, NSString *> *)headers 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 headers:(nullable NSDictionary <NSString *, NSString *> *)headers 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 headers:(nullable NSDictionary <NSString *, NSString *> *)headers success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable id )parameters headers:(nullable NSDictionary <NSString *, NSString *> *)headers uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; @end
如上所述,忽略requestSerializer、responseSerializer、securityPolicy等属性,对于外界来说,只需要关注发起不同请求方式的接口:GET、HEAD、POST、PUT、DELETE等。
通常使用方式是创建一个AFHTTPSessionManager的子类,并设置为单例。做一些简单的配置,比如设置通用的Base URL、安全策略等:
1 2 3 4 5 6 7 8 9 10 11 12 static NSString * const AFAppDotNetAPIBaseURLString = @"https://api.app.net/" ;@implementation AFAppDotNetAPIClient + (instancetype )sharedClient { static AFAppDotNetAPIClient *_sharedClient = nil ; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ _sharedClient = [[AFAppDotNetAPIClient alloc] initWithBaseURL:[NSURL URLWithString:AFAppDotNetAPIBaseURLString]]; }); return _sharedClient; } @end
比如GET请求,就可以这样使用:
1 2 3 4 5 [[AFAppDotNetAPIClient sharedClient] GET:@"stream/0/posts/stream/global" parameters:nil headers: nil progress:nil success:^(NSURLSessionDataTask * __unused task, id JSON) { // 成功回调 } failure:^(NSURLSessionDataTask *__unused task, NSError *error) { // 失败回调 }]
使用POST请求上传文件则是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [[AFAppDotNetAPIClient sharedClient] POST:@"stream/0/posts/upload" parameters:nil headers:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { // 获取文件数据 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"userfile.jpg" ofType:nil] if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) { NSData *fileData = [NSData dataWithContentsOfFile:filePath] if (fileData ) { // 拼接数据 [formData appendPartWithFileData:fileData name:@"userfile" fileName:[filePath lastPathComponent] mimeType:@"image/jpg" ] } } } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { // 成功回调 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { // 失败回调 }]
上传文件的时候,我们只需要关注怎么使用这个”formData“即可,而不用也不必关心它是什么、有哪些属性,这就是协议的妙处。
2,AFHTTPSessionManager的实现 AFHTTPSessionManager继承自AFURLSessionManager,用于支持HTTP(S)协议的网络请求,对外提供常见的HTTPMethod的接口,并返回NSURLSessionDataTask实例。比如GET请求方式是这么实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id )parameters headers:(nullable NSDictionary <NSString *, NSString *> *)headers progress:(nullable void (^)(NSProgress * _Nonnull))downloadProgress success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters headers:headers uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure]; [dataTask resume]; return dataTask; }
其他请求方式也类似,均指向了统一的入口方法,这个入口方法根据不同的HTTPMethod生成相应的NSURLSessionDataTask实例,这个入口方法做了这些事: (1)借助AFURLRequestSerialization生成NSURLRequest。实际上AFURLRequestSerialization是协议,默认是使用实现这个协议的AFHTTPRequestSerializer类生成NSURLRequest。 (2)把外界的headers信息复制到NSURLRequest的请求头中。 (3)调用父类(AFURLSessionManager)的dataTaskWithRequest方法,传入request、uploadProgress、downloadProgress等参数,生成NSURLSessionDataTask并返回。
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 - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable id )parameters headers:(nullable NSDictionary <NSString *, NSString *> *)headers uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure { NSError *serializationError = nil ; NSMutableURLRequest *request = [self .requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self .baseURL] absoluteString] parameters:parameters error:&serializationError]; for (NSString *headerField in headers.keyEnumerator) { [request setValue:headers[headerField] forHTTPHeaderField:headerField]; } if (serializationError) { if (failure) { dispatch_async (self .completionQueue ?: dispatch_get_main_queue(), ^{ failure(nil , serializationError); }); } return nil ; } __block NSURLSessionDataTask *dataTask = nil ; dataTask = [self dataTaskWithRequest:request uploadProgress:uploadProgress downloadProgress:downloadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) failure(dataTask, error); } else { if (success) success(dataTask, responseObject); } }]; return dataTask; }
可见,AFHTTPSessionManager作为对外的接口,内部实现也非常简洁,复杂的逻辑都交给了上层实现。最关键的有两部分,一是通过AFURLRequestSerialization生成NSURLRequest,二是通过父类(AFURLSessionManager)处理NSURLRequest并生成NSURLSessionDataTask。
3,父类AFURLSessionManager AFURLSessionManager本质上高度封装了NSURLSession的逻辑,从接口层面可以看到: (1)管理NSURLSession,遵守其协议,作为代理实现它的相关回调。 (2)外界可以配置请求序列化实例(AFURLRequestSerialization)和响应序列化实例(AFURLResponseSerialization),进而处理Request和Response。 (3)提供普通任务、上传任务、下载任务的构建方法并统一管理所有的任务,通过Block形式对外提供各种任务的各阶段的回调。 (4)利用NSProgress对外提供上传任务和下载任务的进度。 (5)网络请求安全策略验证。
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 @interface AFURLSessionManager : NSObject <NSURLSessionDelegate , NSURLSessionTaskDelegate , NSURLSessionDataDelegate , NSURLSessionDownloadDelegate , NSSecureCoding , NSCopying >@property (readonly , nonatomic , strong ) NSURLSession *session;@property (readonly , nonatomic , strong ) NSOperationQueue *operationQueue;@property (nonatomic , strong ) id <AFURLResponseSerialization> responseSerializer;@property (nonatomic , strong ) AFSecurityPolicy *securityPolicy;#if !TARGET_OS_WATCH @property (readwrite , nonatomic , strong ) AFNetworkReachabilityManager *reachabilityManager;#endif @property (readonly , nonatomic , strong ) NSArray <NSURLSessionTask *> *tasks;@property (readonly , nonatomic , strong ) NSArray <NSURLSessionDataTask *> *dataTasks;@property (readonly , nonatomic , strong ) NSArray <NSURLSessionUploadTask *> *uploadTasks;@property (readonly , nonatomic , strong ) NSArray <NSURLSessionDownloadTask *> *downloadTasks;@property (nonatomic , strong , nullable ) dispatch_queue_t completionQueue;@property (nonatomic , strong , nullable ) dispatch_group_t completionGroup;- (instancetype )initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER ; - (void )invalidateSessionCancelingTasks:(BOOL )cancelPendingTasks resetSession:(BOOL )resetSession; - (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; - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; - (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task; - (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task; - (void )setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block; - (void )setTaskNeedNewBodyStreamBlock:(nullable NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block; - (void )setTaskWillPerformHTTPRedirectionBlock:(nullable NSURLRequest * _Nullable (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block; - (void )setTaskDidSendBodyDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block; - (void )setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error))block; #if AF_CAN_INCLUDE_SESSION_TASK_METRICS - (void )setTaskDidFinishCollectingMetricsBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * _Nullable metrics))block AF_API_AVAILABLE(ios(10 ), macosx(10.12 ), watchos(3 ), tvos(10 )); #endif - (void )setDataTaskDidReceiveResponseBlock:(nullable NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block; - (void )setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block; - (void )setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block; - (void )setDataTaskWillCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block; - (void )setDidFinishEventsForBackgroundURLSessionBlock:(nullable void (^)(NSURLSession *session))block AF_API_UNAVAILABLE(macos); - (void )setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block; - (void )setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block; - (void )setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block; - (void )setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block; - (void )setAuthenticationChallengeHandler:(id (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition , NSURLCredential * _Nullable)))authenticationChallengeHandler; @end
AFURLSessionManager暴露出很多接口,很大一部分是NSURLSession的代理以Block形式透传,外界可以更加优雅这些回调。
4,AFURLSessionManager的实现 NSURLSession是网络请求的核心,AFURLSessionManager强引用NSURLSession并作为它的delegate,而Session因为其场景的特殊性,它的delegate是retain修饰的强引用:
1 @property (nullable , readonly , retain ) id <NSURLSessionDelegate > delegate;
二者循环引用,这也就是为什么最开始推荐使用单例方式使用AFURLSessionManager的原因,否则随意的创建AFURLSessionManager,而不及时打破循环引用,会造成内存泄漏。
类似delegate是强引用的例子在Cocoa框架中还有许多,比如CAAnimation(@property(nullable, strong) id <CAAnimationDelegate> delegate;
),因为动画和网络请求是在异步执行,而异步执行中delegate随时可能会被销毁,所以需要设置delegate为强引用来保证在网络请求、动画的整个过程中都有着陆点、响应都能正确进行。
在构造方法中可以看到,除了一些基本属性的配置,AFURLSessionManager维护了一份可变字典mutableTaskDelegatesKeyedByTaskIdentifier,把任务与任务的代理建立映射,分门别类地把普通任务、下载任务、上传任务及其代理统一管理。
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 - (instancetype )initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self .operationQueue.maxConcurrentOperationCount = 1 ; self .responseSerializer = [AFJSONResponseSerializer serializer]; self .securityPolicy = [AFSecurityPolicy defaultPolicy]; #if !TARGET_OS_WATCH self .reachabilityManager = [AFNetworkReachabilityManager sharedManager]; #endif self .mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; [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 ; }
为了逻辑清晰,作者创建了AFURLSessionManagerTaskDelegate用来专门处理Task的代理逻辑。但是为了接口统一,统筹处理回调接口,AFURLSessionManager既作为NSURLSession的代理,也作为所有任务的代理。也即,AFURLSessionManager起到桥梁的作用。 纵观全部,AFURLSessionManager主要做了两件核心的事: (1)管理所有Task及TaskDelegate。通过一份Map,将Task与TaskDelegate建立映射,自身虽然是所有Task的代理,但是却把具体实现转交给TaskDelegate处理。 (2)协调NSURLSession,自身作为NSURLSession的代理,实现协议中的方法并以Block回调的方式作为接口透出。
管理所有Task及TaskDelegate AFURLSessionManager对外提供了四个接口:获取普通任务、获取上传任务、获取下载任务、获取全部任务,但是实现上怎么获取NSURLSession的任务呢,可以发现,NSURLSession提供了getTasksWithCompletionHandler
方法获取它的各种Task。
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 - (NSArray *)tasks { return [self tasksForKeyPath:NSStringFromSelector (_cmd)]; } - (NSArray *)dataTasks { return [self tasksForKeyPath:NSStringFromSelector (_cmd)]; } - (NSArray *)uploadTasks { return [self tasksForKeyPath:NSStringFromSelector (_cmd)]; } - (NSArray *)downloadTasks { return [self tasksForKeyPath:NSStringFromSelector (_cmd)]; } - (NSArray *)tasksForKeyPath:(NSString *)keyPath { __block NSArray *tasks = nil ; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0 ); [self .session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { if ([keyPath isEqualToString:NSStringFromSelector (@selector (dataTasks))]) { tasks = dataTasks; } else if ([keyPath isEqualToString:NSStringFromSelector (@selector (uploadTasks))]) { tasks = uploadTasks; } else if ([keyPath isEqualToString:NSStringFromSelector (@selector (downloadTasks))]) { tasks = downloadTasks; } else if ([keyPath isEqualToString:NSStringFromSelector (@selector (tasks))]) { tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self" ]; } dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return tasks; }
_cmd
就是方法的Selector,作者偷了个懒。最终是通过tasksForKeyPath方法来获取相应种类的任务数组。这个方法用到了信号量,起到把异步方法getTasksWithCompletionHandler转同步的作用。KVC的中@"@unionOfArrays.self"
语法糖,实则相当于:
1 2 3 4 NSMutableArray *array = [NSMutableArray arrayWithArray:dataTasks]; [array addObjectsFromArray:uploadTasks]; [array addObjectsFromArray:downloadTasks]; tasks = array .copy ;
对于管理的每一个任务,我们比较关心的是它的状态,当任务状态改变的时候需要发出全局的通知,供外界消费,目前有6种状态的通知:
Session失效:AFURLSessionDidInvalidateNotification 任务完成:AFNetworkingTaskDidCompleteNotification 任务恢复:AFNetworkingTaskDidResumeNotification 任务挂起:AFNetworkingTaskDidSuspendNotification 下载任务临时文件移动失败:AFURLSessionDownloadTaskDidFailToMoveFileNotification 下载任务临时文件移动成功:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification
很轻松的,因为AFURLSessionManager是NSURLSession和所有任务的代理,可以在相关代理方法中发出这个任务的状态通知,比如Session失效:
1 2 3 4 5 6 - (void)URLSession :(NSURLSession *)session didBecomeInvalidWithError :(NSError *)error { if (self.sessionDidBecomeInvalid) { self .sessionDidBecomeInvalid (session, error); } [[NSNotificationCenter defaultCenter] postNotificationName :AFURLSessionDidInvalidateNotification object :session ]; }
实际上AFURLSessionManager自己只发出了AFURLSessionDidInvalidateNotification、AFNetworkingTaskDidResumeNotification和AFNetworkingTaskDidSuspendNotification这三种通知,另外三种是交给AFURLSessionManagerTaskDelegate发出的。
任务已经清晰明了了,那如何管理所有的任务的代理呢?首先获取每个任务的唯一标识符taskIdentifier,再建立一个可变字典,把taskIdentifier与任务的代理一一对应。在多线程环境中,管理这些任务的代理自然要考虑线程安全问题。而NSMutableDictionary并不是线程安全的,所以在读写字典的时候需要加一个锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task { AFURLSessionManagerTaskDelegate *delegate = nil ; [self .lock lock]; delegate = self .mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)]; [self .lock unlock]; return delegate; } - (void )setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { [self .lock lock]; self .mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; [self addNotificationObserverForTask:task]; [self .lock unlock]; } - (void )removeDelegateForTask:(NSURLSessionTask *)task { [self .lock lock]; [self removeNotificationObserverForTask:task]; [self .mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self .lock unlock]; }
到此为止,AFURLSessionManager可以控制所有种类的任务、控制任务对应的代理以及处理所有任务的状态了。
协调NSURLSession AFURLSessionManager核心逻辑都是围绕着NSURLSession处理的,网络请求的功能也是依赖它实现的。一个AFURLSessionManager只需要一个NSURLSession实例,所以在创建NSURLSession的时候作者使用了@synchronized
锁,避免重复创建。
1 2 3 4 5 6 7 8 - (NSURLSession *)session { @synchronized (self ) { if (!_session) { _session = [NSURLSession sessionWithConfiguration:self .sessionConfiguration delegate:self delegateQueue:self .operationQueue]; } } return _session; }
AFURLSessionManager实现了所有必要的代理方法,并在代理方法中调用AFURLSessionManagerTaskDelegate的“同名”方法,同时处理外界的接口回调。以taskDidComplete属性为例,首先根据Task获取对应的AFURLSessionManagerTaskDelegate,然后调用AFURLSessionManagerTaskDelegate的同名回调,最后如果外界有设置回调则调用。对于Session的代理处理,几乎都是这三个逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 - (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); } }
但是有一个代理方法的实现需要着重看一下,那就是对质询的处理。先回顾下HTTPS的主要流程:
https://juejin.cn/post/6844903906560966670 验证流程: 1,客户端生成随机数Client random, 声明支持的加密方式, 发送给服务端. (ClientHello) 2,服务端确认加密方式, 生成随机数Server random, 给出服务端证书, 发送给客户端. (SeverHello, SeverHello Done) 3,如果服务端要求双向认证,则客户端需要提供客户端证书给服务端(Client Key Exchange); 接着客户端验证服务端证书是否合法, 生成随机数Pre-Master, 并使用服务端证书中的公钥进行加密, 发送给服务端. (Certificate Verify) 4,服务端使用自己的证书私钥, 解密客户端发送来的加密信心, 得到Per-Master 5,客户端和服务端此时拥有三个相同的随机数, 按照相同算法生成对话私钥, 彼此互相使用对话私钥加密Finish信息互相确认私钥正确性, 握手完成.
中间人攻击的类型 方式一:用CA申请的证书 验证证书的信息摘要是否被修改。处理权威机构签发的证书对于权威机构签发的证书, 这类证书上面会声明自己是由哪一个CA机构(或CA的子机构)签发, 而对应的CA机构也有自己的CA证书, 在手机出厂之前就被安装进系统里了,这样对于权威机构签发的服务器证书,只要从系统里找一下服务器证书对应的CA证书, 拿CA证书的公钥解密一下服务器证书的签名, 解密出的Hash是不是和服务器携带的数据部分运算出的Hash一致, 即可证明服务器证书是合法的。
比如A和B通信,B的公钥和数字证书被O截取。而O原先已将自己的公钥上传CA,并得到了CA签发的一张等效的数字证书。于是O将B的公钥和证书一起替换成O的公钥和O的证书,再发给A。A能够识别自己正在遭受中间人攻击吗? 服务方向第三方机构CA提交公钥、组织信息、个人信息(域名)等信息并申请认证,CA通过线上、线下等多种手段验证申请者提供信息的真实性,如组织是否存在、企业是否合法,是否拥有域名的所有权等。证书 = 公钥(服务方生成密码对中的公钥)+申请者与颁发者信息+签名(用CA机构生成的密码对的私钥进行签名。
方式二:用自签名的证书 验证证书的签发机构是否受信任。
这个方法的使用场景有两个: (1)When a remote server asks for client certificates or Windows NT LAN Manager (NTLM) authentication, to allow your app to provide appropriate credentials (2)When a session first establishes a connection to a remote server that uses SSL or TLS, to allow your app to verify the server’s certificate chain If you do not implement this method, the session calls its delegate’s URLSession:task:didReceiveChallenge:completionHandler: method instead. 也即双向验证时提供客户端证书和验证服务端证书。主要的应用场景是验证服务端证书,服务端证书分为两种情况:权威机构颁发的证书和自制的证书。
如何处理证书:
1 2 3 4 NSURLSessionAuthChallengeUseCredential = 0 , 使用该证书 安装该证书NSURLSessionAuthChallengePerformDefaultHandling = 1 , 默认采用的方式, 该证书被忽略NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2 , 取消请求, 证书忽略NSURLSessionAuthChallengeRejectProtectionSpace = 3 , 拒绝
质询验证方式:
1 2 3 NSURLAuthenticationMethodHTTPBasic NSURLAuthenticationMethodClientCertificate NSURLAuthenticationMethodServerTrust
CA颁发的证书验证逻辑是校验证书的合法性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (void )URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition , NSURLCredential * _Nullable))completionHandler { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust ]) { SecTrustResultType result; SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result); if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) { NSLog (@"合法" ); completionHandler(NSURLSessionAuthChallengePerformDefaultHandling , nil ); } else { NSLog (@"不合法" ); completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil ); } } completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil ); }
自签名的证书验证逻辑是校验证书的合法性和一致性:
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 - (void )URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition , NSURLCredential * _Nullable))completionHandler { NSURLCredential *serverCredential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; NSData *certificateData = [NSData dataWithContentsOfFile:[NSBundle .mainBundle pathForResource:@"certificate" ofType:@"der" ]]; NSMutableArray *pinnedCertificates = [NSMutableArray array]; [pinnedCertificates addObject:(__bridge_transfer id )SecCertificateCreateWithData(NULL , (__bridge CFDataRef )certificateData)]; SecTrustSetAnchorCertificates(challenge.protectionSpace.serverTrust, (__bridge CFArrayRef )pinnedCertificates); SecTrustResultType result; SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result); if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) { NSLog (@"合法" ); CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust); NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger )certificateCount]; for (CFIndex i = 0 ; i < certificateCount; i++) { SecCertificateRef certificate = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i); [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)]; } NSArray *serverCertificates = [NSArray arrayWithArray:trustChain]; if ([serverCertificates containsObject:certificateData]) { NSLog (@"服务器证书和本地证书相同" ); completionHandler(NSURLSessionAuthChallengeUseCredential , serverCredential); } else { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil ); } } else { NSLog (@"不合法" ); completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge , nil ); } }
再看一下AFN是怎么处理的:
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 - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { BOOL evaluateServerTrust = NO ; NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling ; NSURLCredential *credential = nil ; if (self .authenticationChallengeHandler) { id result = self .authenticationChallengeHandler(session, task, challenge, completionHandler); if (result == nil ) { return ; } else if ([result isKindOfClass:NSError .class]) { objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN); disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge ; } else if ([result isKindOfClass:NSURLCredential .class]) { credential = result; disposition = NSURLSessionAuthChallengeUseCredential ; } else if ([result isKindOfClass:NSNumber .class]) { disposition = [result integerValue]; NSAssert (disposition == NSURLSessionAuthChallengePerformDefaultHandling || disposition == NSURLSessionAuthChallengeCancelAuthenticationChallenge || disposition == NSURLSessionAuthChallengeRejectProtectionSpace , @"" ); evaluateServerTrust = disposition == NSURLSessionAuthChallengePerformDefaultHandling && [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust ]; } else { @throw [NSException exceptionWithName:@"Invalid Return Value" reason:@"The return value from the authentication challenge handler must be nil, an NSError, an NSURLCredential or an NSNumber." userInfo:nil ]; } } else { evaluateServerTrust = [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust ]; } if (evaluateServerTrust) { if ([self .securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential ; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, [self serverTrustErrorForServerTrust:challenge.protectionSpace.serverTrust url:task.currentRequest.URL], OBJC_ASSOCIATION_RETAIN); disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge ; } } if (completionHandler) { completionHandler(disposition, credential); } }
除此之外,有一块代码让人感到非常严谨。AFURLSessionManager重写了respondsToSelector方法,一些selector是否实现的判断逻辑映射到相应的外界接口,也即AFURLSessionManager是否响应某些方法取决于外界是否设置了相应的回调,可谓巧妙又严谨。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (BOOL)respondsToSelector: (SEL)selector { if (selector == @selector(URLSession: didReceiveChallenge: completionHandler: )) { return self .sessionDidReceiveAuthenticationChallenge != nil ; } else if (selector == @selector(URLSession: task: willPerformHTTPRedirection: newRequest: completionHandler: )) { return self .taskWillPerformHTTPRedirection != nil ; } else if (selector == @selector(URLSession: dataTask: didReceiveResponse: completionHandler: )) { return self .dataTaskDidReceiveResponse != nil ; } else if (selector == @selector(URLSession: dataTask: willCacheResponse: completionHandler: )) { return self .dataTaskWillCacheResponse != nil ; } else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession: )) { return self .didFinishEventsForBackgroundURLSession != nil ; } return [[self class ] instancesRespondToSelector :selector ]; }
TaskDelegate的引入 在创建任务的时候,同时创建与其对应的AFURLSessionManagerTaskDelegate,由这个Delegate处理任务的代理的逻辑,做到了职责分离,分工明确。addDelegateForDataTask方法把Task与TaskDelegate建立关联,其实是基本属性的配置。
1 2 3 4 5 6 7 8 9 10 11 12 - (void )addDelegateForDataTask:(NSURLSessionDataTask *)dataTask uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask]; delegate.manager = self ; delegate.completionHandler = completionHandler; dataTask.taskDescription = self .taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask]; delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; }
在AFURLSessionManagerTaskDelegate的接口文件中可以看到,它遵循了NSURLSessionTaskDelegate
、NSURLSessionDataDelegate
和NSURLSessionDownloadDelegate
三种协议,不引用Task,弱引用AFURLSessionManager。构造方法initWithTask要求传入Task但是却不持有,主要是因为这个Task是给NSProgress用的,自身全程不需要用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate , NSURLSessionDataDelegate , NSURLSessionDownloadDelegate >- (instancetype )initWithTask:(NSURLSessionTask *)task; @property (nonatomic , weak ) AFURLSessionManager *manager;@property (nonatomic , strong ) NSMutableData *mutableData;@property (nonatomic , strong ) NSProgress *uploadProgress;@property (nonatomic , strong ) NSProgress *downloadProgress;@property (nonatomic , copy ) NSURL *downloadFileURL;#if AF_CAN_INCLUDE_SESSION_TASK_METRICS @property (nonatomic , strong ) NSURLSessionTaskMetrics *sessionTaskMetrics AF_API_AVAILABLE(ios(10 ), macosx(10.12 ), watchos(3 ), tvos(10 ));#endif @property (nonatomic , copy ) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;@property (nonatomic , copy ) AFURLSessionTaskProgressBlock uploadProgressBlock;@property (nonatomic , copy ) AFURLSessionTaskProgressBlock downloadProgressBlock;@property (nonatomic , copy ) AFURLSessionTaskCompletionHandler completionHandler;@end
TaskDelegate的逻辑 AFURLSessionManagerTaskDelegate有三个核心逻辑,一个是处理上传任务和下载任务的进度,另一个是下载任务的收尾,还有一个是所有任务完成后的收尾。
处理任务的进度 作者利用NSProgress来表征任务的进度信息(上传进度、下载进度)。从TaskDelegate的构造方法中可以看到,创建一个上传任务的NSProgress和一个下载任务的NSProgress,在NSProgress的取消回调、暂停回调、恢复回调中设置对应的改变任务状态的方法。利用KVO监听任务的fractionCompleted属性(某个任务已完成量占总量的比例),把任务进度的实时改变传递出去。
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 - (instancetype )initWithTask:(NSURLSessionTask *)task { self = [super init]; if (!self ) { return nil ; } _mutableData = [NSMutableData data]; _uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil ]; _downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil ]; __weak __typeof__(task) weakTask = task; for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ]) { progress.totalUnitCount = NSURLSessionTransferSizeUnknown ; progress.cancellable = YES ; progress.cancellationHandler = ^{ [weakTask cancel]; }; progress.pausable = YES ; progress.pausingHandler = ^{ [weakTask suspend]; }; #if AF_CAN_USE_AT_AVAILABLE if (@available(macOS 10.11 , *)) #else if ([progress respondsToSelector:@selector (setResumingHandler:)]) #endif { progress.resumingHandler = ^{ [weakTask resume]; }; } [progress addObserver:self forKeyPath:NSStringFromSelector (@selector (fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL ]; } return self ; } - (void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary <NSString *,id > *)change context:(void *)context { if ([object isEqual:self .downloadProgress]) { if (self .downloadProgressBlock) { self .downloadProgressBlock(object); } } else if ([object isEqual:self .uploadProgress]) { if (self .uploadProgressBlock) { self .uploadProgressBlock(object); } } }
一切布置妥当,只需要适时更改NSProgress的totalUnitCount和completedUnitCount即可,比如:
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 - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { self .uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend; self .uploadProgress.completedUnitCount = task.countOfBytesSent; } - (void )URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ self .downloadProgress.totalUnitCount = totalBytesExpectedToWrite; self .downloadProgress.completedUnitCount = totalBytesWritten; } - (void )URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{ self .downloadProgress.totalUnitCount = expectedTotalBytes; self .downloadProgress.completedUnitCount = fileOffset; }
这样就做到了完美的进度透出工作。
下载任务的收尾 下载任务的资源下载完毕,先把临时文件移动到目标下载路径,之后发出AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - (void )URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { self .downloadFileURL = nil ; if (self .downloadTaskDidFinishDownloading) { self .downloadFileURL = self .downloadTaskDidFinishDownloading(session, downloadTask, location); if (self .downloadFileURL) { NSError *fileManagerError = nil ; if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self .downloadFileURL error:&fileManagerError]) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } else { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil ]; } } } }
所有任务完成后的收尾 所有任务结束的时候需要做收尾工作,主要逻辑是: (1)配置AFNetworkingTaskDidCompleteNotification通知的userInfo内容,包括性能指标、资源路径、responseObject、错误信息等,并在主线程发出这个通知。 (2)使用dispatch_group,把所有任务的completionHandler放到group中执行。外界可拿到这个group,从而利用dispatch_group_notify获取所有回调完成的时机。
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 - (void )URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error; __strong AFURLSessionManager *manager = self .manager; __block id responseObject = nil ; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer; NSData *data = nil ; if (self .mutableData) { data = [self .mutableData copy ]; self .mutableData = nil ; } #if AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS if (@available(iOS 10 , macOS 10.12 , watchOS 3 , tvOS 10 , *)) { if (self .sessionTaskMetrics) { userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self .sessionTaskMetrics; } } #endif 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]; }); }); }); } }
二、AFHTTPRequestSerializer AFURLRequestSerialization是一种协议,只包含一个方法,定义了根据NSURLRequest和参数(parameters)生成新的NSURLRequest的规则。
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
AFHTTPRequestSerializer遵循了这个AFURLRequestSerialization协议,实际上AFHTTPRequestSerializer也是主要做了这件事:整合请求的请求头、参数、配置等信息,输出一个NSURLRequest。
1,基本使用 AFHTTPRequestSerializer定义了请求的参数、请求头,还有配置,比如缓存策略、超时时间、请求头、编码方式、HTTP管线化等,并且可以通过它创建定制化的NSURLRequest。AFHTTPRequestSerializer还向下派生出两个子类:用于支持内容类型为JSON(Content-Type为application/json
)的AFJSONRequestSerializer,以及用于支持内容类型为属性列表文件(Content-Type为application/x-plist
)的AFPropertyListRequestSerializer。
HTTP管线化 通常默认情况下请求和响应是顺序的,也就是说请求–>得到响应后,再请求。 如果将HTTPShouldUsePipelining设置为YES,则允许不必等到response,就可以再次请求。这个会很大的提高网络请求的效率。 因为客户端无法正确的匹配请求与响应,所以这依赖于’服务器必须保证’,响应的顺序与客户端请求的顺序一致。如果服务器不能保证这一点,那可能导致响应和请求混乱。
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 @interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization >@property (nonatomic , assign ) NSStringEncoding stringEncoding;@property (nonatomic , assign ) BOOL allowsCellularAccess;@property (nonatomic , assign ) NSURLRequestCachePolicy cachePolicy;@property (nonatomic , assign ) BOOL HTTPShouldHandleCookies;@property (nonatomic , assign ) BOOL HTTPShouldUsePipelining;@property (nonatomic , assign ) NSURLRequestNetworkServiceType networkServiceType;@property (nonatomic , assign ) NSTimeInterval timeoutInterval;@property (readonly , nonatomic , strong ) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;+ (instancetype )serializer; - (void )setValue:(nullable NSString *)value forHTTPHeaderField:(NSString *)field; - (nullable NSString *)valueForHTTPHeaderField:(NSString *)field; - (void )setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password; - (void )clearAuthorizationHeader; @property (nonatomic , strong ) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;- (void )setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style; - (void )setQueryStringSerializationWithBlock:(nullable NSString * _Nullable (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block; - (nullable NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable id )parameters error:(NSError * _Nullable __autoreleasing *)error; - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable NSDictionary <NSString *, id > *)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block error:(NSError * _Nullable __autoreleasing *)error; - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(nullable void (^)(NSError * _Nullable error))handler; @end
AFHTTPRequestSerializer对外输出三种类型的NSURLRequest,分别是普通类型、上传数据类型和下载数据类型,为了支持表单上传的Request,作者还定义了一种协议:AFMultipartFormData。使用这个协议中定义的方法用来做数据上传工作,比如通过传入文件的路径、传入NSInputStream、或者文件的NSData等上传数据。实际上是AFStreamingMultipartFormData实现了这个协议中的方法,做了这些逻辑,但是对于外界来说,这个类的隐藏的,外界只看到了接口,只需要调用接口即可。
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 @protocol AFMultipartFormData - (BOOL )appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError * _Nullable __autoreleasing *)error; - (BOOL )appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType error:(NSError * _Nullable __autoreleasing *)error; - (void )appendPartWithInputStream:(nullable NSInputStream *)inputStream name:(NSString *)name fileName:(NSString *)fileName length:(int64_t)length mimeType:(NSString *)mimeType; - (void )appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType; - (void )appendPartWithFormData:(NSData *)data name:(NSString *)name; - (void )appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers body:(NSData *)body; - (void )throttleBandwidthWithPacketSize:(NSUInteger )numberOfBytes delay:(NSTimeInterval )delay; @end
2,AFHTTPRequestSerializer的实现 通过上面的接口可以发现,其实AFHTTPRequestSerializer做的就是发起请求前的准备工作,最终输出的NSURLRequest是它做的工作的结果,但是它并没有把NSURLRequest暴露出来。换句话说,这个NSURLRequest是一个方法的返回值,但是却没有作为AFHTTPRequestSerializer的属性。这样,就牵扯到属性映射问题。
NSURLRequest的六个属性 AFHTTPRequestSerializer有六个属性比较特殊,分别是allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType和timeoutInterval。实际上这六个属性会映射到NSURLRequest中的同名属性,这下很清晰了,设置AFHTTPRequestSerializer的这些属性,就是设置NSURLRequest的同等属性。但是当设置这六个属性的值的时候,AFHTTPRequestSerializer本身没有NSURLRequest这个成员变量,没法把这些属性值直接设置到NSURLRequest上面,所以才有这一大块逻辑。看看作者如何处理:
在AFHTTPRequestSerializer初始化的时候对这六个属性添加KVO监听,并维护一个动态更新的列表mutableObservedChangedKeyPaths(NSSet类型)。当外界对某个属性设值的时候,把这个属性存储到列表中,当外界对某个属性设空值(nil
)的时候,把这个属性从列表中移除。
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 - (instancetype )init { self = [super init]; if (!self ) { return nil ; } .... self .mutableObservedChangedKeyPaths = [NSMutableSet set]; for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString (keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } } return self ; } - (void )observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id )object change:(NSDictionary *)change context:(void *)context { if (context == AFHTTPRequestSerializerObserverContext) { if ([change[NSKeyValueChangeNewKey ] isEqual:[NSNull null]]) { [self .mutableObservedChangedKeyPaths removeObject:keyPath]; } else { [self .mutableObservedChangedKeyPaths addObject:keyPath]; } } }
这样,mutableObservedChangedKeyPaths永远是最新的外界设置过属性的列表。最后在创建Request的时候,将这些设置信息同步到NSURLRequest中:
1 2 3 for (NSString *keyPath in self .mutableObservedChangedKeyPaths) { [mutableRequest set Value:[self valueForKeyPath:keyPath] for Key:keyPath]; }
这里就有疑问了,为啥要多此一举弄一个KVO,外界有设置过属性,AFHTTPRequestSerializer本身就会存储,在创建Request的时候直接拿出来同步不就好了,多个KVO的好处是啥?
请求头的多读单写 可变字典mutableHTTPRequestHeaders是内部请求头的信息集合,在多线程环境下,需要做到多读单写,保证线程安全。这里用并发队列+栅栏函数实现。
1,valueForHTTPHeaderField:
和HTTPRequestHeaders
要求有返回值,所以使用同步函数。 2,requestHeaderModificationQueue是自己创建的并发队列,但是读写操作都是使用同步函数,所以这些操作都是在当前线程。 2,setValue:forHTTPHeaderField:
和clearAuthorizationHeader
使用了栅栏函数,实现设值操作和其他操作(取值操作)互斥,其他操作结束后才能有设值操作,设值操作不能并发执行,从而保证线程安全。
再回顾一下栅栏函数:
When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the queue executes the barrier block by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.
所以可以总结为: (1)当使用栅栏函数向队列添加任务时,必须等待队列中已有的任务执行完毕,栅栏函数中的任务才会执行。 (2)必须等待栅栏函数中的任务执行完毕,其他任务才会执行。
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 - (NSDictionary *)HTTPRequestHeaders { NSDictionary __block *value; dispatch_sync (self .requestHeaderModificationQueue, ^{ value = [NSDictionary dictionaryWithDictionary:self .mutableHTTPRequestHeaders]; }); return value; } - (NSString *)valueForHTTPHeaderField:(NSString *)field { NSString __block *value; dispatch_sync (self .requestHeaderModificationQueue, ^{ value = [self .mutableHTTPRequestHeaders valueForKey:field]; }); return value; } - (void )setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { dispatch_barrier_sync(self .requestHeaderModificationQueue, ^{ [self .mutableHTTPRequestHeaders setValue:value forKey:field]; }); } - (void )clearAuthorizationHeader { dispatch_barrier_sync(self .requestHeaderModificationQueue, ^{ [self .mutableHTTPRequestHeaders removeObjectForKey:@"Authorization" ]; }); }
上面的读写操作可以总结为下面三条: (1)可以同时有多个读操作,读操作可以并发执行。 (2)不能同时有多个写操作,写操作串行执行,在写操作之前,读操作都完成。 (3)写操作与读操作、其他写操作是互斥的(读操作的时候,不能有写操作,写操作的时候,不能有读操作)。
回到逻辑上,mutableHTTPRequestHeaders存储着请求头的信息,在创建NSURLRequest的时候,把这个信息同步到NSURLRequest上即可。
1 2 3 4 5 [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (! [request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field] } }]
NSURLRequest构建 对于一般的请求,主要有四块逻辑: (1)NSURLRequest的参数配置(就是上文所说的mutableObservedChangedKeyPaths的使用)。 (2)请求头的处理,把请求头字段和值逐一设置到Request中。 (3)请求参数的序列化,当外界有特定的序列化方式时,调用外界的接口生成字符串,否则采用默认的方式,即通过”&“拼接成字符串。 (4)请求参数的位置,默认情况下对于GET
, HEAD
和 DELETE
这三种请求方式,参数串是放在URL中,其他的情况是放在HTTP Body中。
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 - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id )parameters error:(NSError *__autoreleasing *)error { NSURL *url = [NSURL URLWithString:URLString]; NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; mutableRequest.HTTPMethod = method; for (NSString *keyPath in self .mutableObservedChangedKeyPaths) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; return mutableRequest; } - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id )parameters error:(NSError *__autoreleasing *)error { NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self .HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; NSString *query = nil ; if (parameters) { if (self .queryStringSerialization) { NSError *serializationError; query = self .queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) *error = serializationError; return nil ; } } else { switch (self .queryStringSerializationStyle) { case AFHTTPRequestQueryStringDefaultStyle: query = AFQueryStringFromParameters(parameters); break ; } } } if ([self .HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { if (query && query.length > 0 ) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@" , query]]; } } else { if (!query) { query = @"" ; } if (![mutableRequest valueForHTTPHeaderField:@"Content-Type" ]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type" ]; } [mutableRequest setHTTPBody:[query dataUsingEncoding:self .stringEncoding]]; } return mutableRequest; }
requestWithMethod:URLString:parameters:error:
这个方法是对外的接口,用于创建普通的请求;requestBySerializingRequest:withParameters:error:
这个方法是内部私有的,负责序列化参数。
普通的Request是这样处理的,特别地,对于AFJSONRequestSerializer和AFPropertyListRequestSerializer,区别就在于设置到HTTPBody中的Data格式不同。
1 2 3 4 5 6 7 NSData *jsonData = [NSJSONSerialization dataWithJSONObject: parameters options: self.writingOptions error: error]; [mutableRequest setHTTPBody: jsonData]; NSData *plistData = [NSPropertyListSerialization dataWithPropertyList: parameters format: self.format options: self.writeOptions error: error]; [mutableRequest setHTTPBody: plistData];
构建Request中比较关键的是如何处理请求的参数。 外界传入的参数是多种类型的,并不一定是字符串,可能是容器类型,比如字典(NSDictionary)、数组(NSArray)、集合(NSSet)等,对于复杂的参数,比如:@{@"name": @"Jack", @"tag_list": @[@"apple", @"orange"], @"tag_info": @{@"super": @"Bob", @"child": @"Aaron"}}
处理后的结果是:name=Jack&tag_info%5Bchild%5D=Aaron&tag_info%5Bsuper%5D=Bob&tag_list%5B%5D=apple&tag_list%5B%5D=orange
解码后是:name=Jack&tag_info[child]=Aaron&tag_info[super]=Bob&tag_list[]=apple&tag_list[]=orange
知道结果了,再看其中的实现就比较容易了。对于NSDictionary,逐个解析,并把外键key和内键nestedKey结合,形成key[nestedKey]=nestedValue
;对于NSArray,逐个拆解,形成key[]=nestedValue
;对于NSSet,逐个遍历递归解析它的元素的类型;对于其他类型,key和value就已经确定了,做编码后的拼接:[NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]
。实现逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if ([value isKindOfClass: [NSDictionary class ]]) { NSDictionary *dictionary = value; for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors: @[ sortDescriptor ]]) { id nestedValue = dictionary[nestedKey]; if (nestedValue) { [mutableQueryStringComponents addObjectsFromArray: AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]" , key, nestedKey] : nestedKey), nestedValue)]; } } } else if ([value isKindOfClass: [NSArray class ]]) { NSArray *array = value; for (id nestedValue in array) { [mutableQueryStringComponents addObjectsFromArray: AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat: @"%@[]" , key], nestedValue)]; } } else if ([value isKindOfClass: [NSSet class ]]) { NSSet *set = value; for (id obj in [set sortedArrayUsingDescriptors: @[ sortDescriptor ]]) { [mutableQueryStringComponents addObjectsFromArray: AFQueryStringPairsFromKeyAndValue(key, obj)]; } } else { [mutableQueryStringComponents addObject: [[AFQueryStringPair alloc] initWithField: key value: value]]; }
对于下载类型的请求,因为没有参数(parameters),逻辑比较独立,借助NSInputStream和NSOutputStream实现数据下载。从HTTPBodyStream创建输入流,再从文件存储路径中创建输出流,在子线程以默认优先级实现流的转换。
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 - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(void (^)(NSError *error))handler { NSInputStream *inputStream = request.HTTPBodyStream; NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO ]; __block NSError *error = nil ; dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode ]; [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode ]; [inputStream open]; [outputStream open]; while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) { uint8_t buffer[1024 ]; NSInteger bytesRead = [inputStream read:buffer maxLength:1024 ]; if (inputStream.streamError || bytesRead < 0 ) { error = inputStream.streamError; break ; } NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger )bytesRead]; if (outputStream.streamError || bytesWritten < 0 ) { error = outputStream.streamError; break ; } if (bytesRead == 0 && bytesWritten == 0 ) { break ; } } [outputStream close]; [inputStream close]; if (handler) { dispatch_async (dispatch_get_main_queue(), ^{ handler(error); }); } }); NSMutableURLRequest *mutableRequest = [request mutableCopy]; mutableRequest.HTTPBodyStream = nil ; return mutableRequest; }
对于上传类型的请求,它是需要参数(parameters)的,所以先利用构造普通请求的方式获取一个NSMutableURLRequest,再追加自己专有的上传逻辑。它创建了一个实现AFMultipartFormData协议的类AFStreamingMultipartFormData,由它负责接收相关参数和处理上传逻辑: (1)创建AFStreamingMultipartFormData实例,把Query参数信息同步到这个AFStreamingMultipartFormData中。 (2)外界传入需要上传的数据,AFStreamingMultipartFormData处理上传逻辑。 (3)设置HTTPBodyStream、Content-Type、Content-Length等。
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 - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block error:(NSError *__autoreleasing *)error { NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error]; __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding ]; if (parameters) { for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { NSData *data = nil ; if ([pair.value isKindOfClass:[NSData class ]]) { data = pair.value; } else if ([pair.value isEqual:[NSNull null]]) { data = [NSData data]; } else { data = [[pair.value description] dataUsingEncoding:self .stringEncoding]; } if (data) { [formData appendPartWithFormData:data name:[pair.field description]]; } } } if (block) { block(formData); } return [formData requestByFinalizingMultipartFormData]; }
外界拿到这个formData就可以这样使用:
1 [formData appendPartWithFileURL: [NSURL fileURLWithPath: @"/Users/User/Desktop/main.png" ] name: @"file" fileName: @"main.png" mimeType: @"image/png" error: nil];
这样来看,其实比较核心的就是AFStreamingMultipartFormData的实现。
AFStreamingMultipartFormData给外界暴露出多种方式的表单上传接口,接收各种参数,最终在内部创建一个AFHTTPBodyPart实例,AFHTTPBodyPart就是各种信息的聚合者并负责数据读取,接收各种类型的数据,这些不同类型的数据最终会统一为NSInputStream类型。最后AFStreamingMultipartFormData把AFHTTPBodyPart拼接到AFMultipartBodyStream中,由AFMultipartBodyStream负责整合所有的NSInputStream。
AFHTTPBodyPart接收必须的参数,用body属性存储”数据“,这个body可以是输入流(NSInputStream)、二进制数据(NSData)以及文件路径(NSURL)。
1 2 3 4 5 6 7 8 9 10 11 AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = mutableHeadersbodyPart.boundary = self.boundary; // 设置AFHTTPBodyPart的body为fileURL // 设置AFHTTPBodyPart的body为inputStream:bodyPart.body = inputStreambodyPart.body = fileURLbodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue]// 拼接bodyPart到AFMultipartBodyStream中 [self.bodyStream appendHTTPBodyPart:bodyPart];
最终把不同类型的数据统一为NSInputStream类型,并由这个NSInputStream负责上传数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (NSInputStream *)inputStream { if (!_inputStream) { if ([self .body isKindOfClass:[NSData class ]]) { _inputStream = [NSInputStream inputStreamWithData:self .body]; } else if ([self .body isKindOfClass:[NSURL class ]]) { _inputStream = [NSInputStream inputStreamWithURL:self .body]; } else if ([self .body isKindOfClass:[NSInputStream class ]]) { _inputStream = self .body; } else { _inputStream = [NSInputStream inputStreamWithData:[NSData data]]; } } return _inputStream; }
而继承自NSInputStream的AFMultipartBodyStream负责统筹所有的AFHTTPBodyPart中的输入流:
1 2 3 4 5 6 7 8 9 10 11 12 @interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate >@property (nonatomic , assign ) NSUInteger numberOfBytesInPacket;@property (nonatomic , assign ) NSTimeInterval delay;@property (nonatomic , strong ) NSInputStream *inputStream;@property (readonly , nonatomic , assign ) unsigned long long contentLength;@property (readonly , nonatomic , assign , getter = isEmpty) BOOL empty;- (instancetype )initWithStringEncoding:(NSStringEncoding )encoding; - (void )setInitialAndFinalBoundaries; - (void )appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart; @end
三、AFURLResponseSerialization 1,简单介绍 AFURLResponseSerialization是一种协议,定义了NSURLResponse转为NSObject对象的方式。
1 2 3 - (nullable id )responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW ;
AFHTTPResponseSerializer作为通用的Response处理器,遵守该协议并实现了该协议的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 @interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization >- (instancetype )init; + (instancetype )serializer; @property (nonatomic , copy , nullable ) NSIndexSet *acceptableStatusCodes;@property (nonatomic , copy , nullable ) NSSet <NSString *> *acceptableContentTypes;- (BOOL )validateResponse:(nullable NSHTTPURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error; @end
根据不同的Response类型,AFHTTPResponseSerializer分别派生出支持JSON类型的AFJSONResponseSerializer、支持XML类型的AFXMLParserResponseSerializer(生成NSXMLParser对象)和AFXMLDocumentResponseSerializer(生成NSXMLDocument对象)、支持属性列表文件类型的AFPropertyListResponseSerializer以及支持图片类型的AFImageResponseSerializer。比较特殊的是支持复合类型的AFCompoundResponseSerializer,它可以同时接受多种类型。
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 @interface AFJSONResponseSerializer : AFHTTPResponseSerializer - (instancetype )init; @property (nonatomic , assign ) NSJSONReadingOptions readingOptions;@property (nonatomic , assign ) BOOL removesKeysWithNullValues;+ (instancetype )serializerWithReadingOptions:(NSJSONReadingOptions )readingOptions; @end @interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer - (instancetype )init; @property (nonatomic , assign ) NSUInteger options;+ (instancetype )serializerWithXMLDocumentOptions:(NSUInteger )mask; @end @interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer - (instancetype )init; @property (nonatomic , assign ) NSPropertyListFormat format;@property (nonatomic , assign ) NSPropertyListReadOptions readOptions;+ (instancetype )serializerWithFormat:(NSPropertyListFormat )format readOptions:(NSPropertyListReadOptions )readOptions; @end @interface AFImageResponseSerializer : AFHTTPResponseSerializer #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH @property (nonatomic , assign ) CGFloat imageScale;@property (nonatomic , assign ) BOOL automaticallyInflatesResponseImage;#endif @end @interface AFCompoundResponseSerializer : AFHTTPResponseSerializer @property (readonly , nonatomic , copy ) NSArray <id <AFURLResponseSerialization>> *responseSerializers;+ (instancetype )compoundSerializerWithResponseSerializers:(NSArray <id <AFURLResponseSerialization>> *)responseSerializers; @end
2,AFHTTPResponseSerializer的实现 作为基类,AFHTTPResponseSerializer最通用的实现逻辑是校验Response和Data的有效性,这里是验证Content-Type和状态码是否在可接受范围之内。
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 - (BOOL )validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error { BOOL responseIsValid = YES ; NSError *validationError = nil ; if ([response isKindOfClass:[NSHTTPURLResponse class ]]) { if (self .acceptableContentTypes && ![self .acceptableContentTypes containsObject:[response MIMEType]] && !([response MIMEType] == nil && [data length] == 0 )) { if ([data length] > 0 && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedStringFromTable (@"Request failed: unacceptable content-type: %@" , @"AFNetworking" , nil ), [response MIMEType]], NSURLErrorFailingURLErrorKey :[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError); } responseIsValid = NO ; } if (self .acceptableStatusCodes && ![self .acceptableStatusCodes containsIndex:(NSUInteger )response.statusCode] && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedStringFromTable (@"Request failed: %@ (%ld)" , @"AFNetworking" , nil ), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long )response.statusCode], NSURLErrorFailingURLErrorKey :[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); responseIsValid = NO ; } } if (error && !responseIsValid) { *error = validationError; } return responseIsValid; }
对于Response与Object的转化,基类AFHTTPResponseSerializer没有特别实现,直接返回了参数Data。
1 2 3 4 5 - (id)responseObjectForResponse:(NSURLResponse *)response data :(NSData *)data error:(NSError *__autoreleasing *)error { // 做校验 [self validateResponse :(NSHTTPURLResponse *)response data :data error :error ]; return data ; }
而对于子类,不同之处就在于这里的Response与Object的转化。AFJSONResponseSerializer的处理是通过NSJSONSerialization把Response转为JSON对象;AFXMLParserResponseSerializer的处理是返回NSXMLParser实例;AFXMLDocumentResponseSerializer的处理是返回NSXMLDocument实例;AFPropertyListResponseSerializer的处理是通过NSPropertyListSerialization把Response转为plist对象。 AFImageResponseSerializer比较特殊,有两种处理方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (id )responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData , AFURLResponseSerializationErrorDomain)) { return nil ; } } #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH if (self .automaticallyInflatesResponseImage) { return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self .imageScale); } else { return AFImageWithDataAtScale(data, self .imageScale); } #else NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data]; NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize ([bitimage pixelsWide], [bitimage pixelsHigh])]; [image addRepresentation:bitimage]; return image; #endif return nil ; }
优化开关关闭时,直接把NSData转为UIImage;优化开关打开时,使用AFInflatedImageFromResponseWithDataAtScale()函数对把NSData转为UIImage。
最后是复合类型AFCompoundResponseSerializer的处理,它可以接收上述介绍的所有类型(的序列化器):只要有一个结果解析成功即可返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - (id)responseObjectForResponse:(NSURLResponse *)response data :(NSData *)data error:(NSError *__autoreleasing *)error { for (id <AFURLResponseSerialization> serializer in self.responseSerializers) { if (![serializer isKindOfClass:[AFHTTPResponseSerializer class ]]) { continue ; } NSError *serializerError = nil; id responseObject = [serializer responseObjectForResponse:response data :data error:&serializerError]; if (responseObject) { if (error) { *error = AFErrorWithUnderlyingError(serializerError, *error); } return responseObject; } } return [super responseObjectForResponse:response data :data error:error]; }
3,UIImage的优化 当我们调用UIImage的方法imageWithData:方法把数据转成UIImage对象后,其实这时UIImage对象还没准备好需要渲染到屏幕的数据,现在的网络图像PNG和JPG都是压缩格式,需要把它们解压转成bitmap后才能渲染到屏幕上,如果不做任何处理,当你把UIImage赋给UIImageView,在渲染之前底层会判断到UIImage对象未解压,没有bitmap数据,这时会在主线程对图片进行解压操作,再渲染到屏幕上。这个解压操作是比较耗时的,如果任由它在主线程做,可能会导致速度慢UI卡顿的问题。
当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都自带这个功能。
AFImageResponseSerializer除了把返回数据解析成UIImage外,还会把图像数据解压,这个处理是在子线程(url_session_manager_processing_queue()
),处理后上层使用返回的UIImage在主线程渲染时就不需要做解压这步操作,主线程减轻了负担,减少了UI卡顿问题。
具体实现上在AFInflatedImageFromResponseWithDataAtScale函数里,创建一个画布,把UIImage画在画布上,再把这个画布保存成UIImage返回给上层。只有JPG和PNG才会尝试去做解压操作,期间如果解压失败,或者遇到CMKY颜色格式的jpg,或者图像太大,就直接返回未解压的图像。
PNG或者JPEG压缩之后的图片文件会比同质量的位图小得多,直接或间接使用UIImageView,或者将图片绘制到CoreGraphics都需要对图片解压缩,对于一个较大的图片,都会占用一定的时间。这一步虽然不可避免,但是我们可以把这个操作放到后台线程,先把图片绘制到CGBitmapContext中,然后从Bitmap直接创建图片。
看看AFInflatedImageFromResponseWithDataAtScale()函数做了啥:
(1)创建位图上下文CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
(2)向位图上下文中绘制图片CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
(3)从位图上下文中取出CGImageRef的图片 CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
(4)CGImageRef类型的图片转为UIImage类型UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
下面是详细逻辑:
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 static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) { if (!data || [data length] == 0 ) { return nil ; } CGImageRef imageRef = NULL ; CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData ((__bridge CFDataRef )data); if ([response.MIMEType isEqualToString:@"image/png" ]) { imageRef = CGImageCreateWithPNGDataProvider (dataProvider, NULL , true , kCGRenderingIntentDefault); } else if ([response.MIMEType isEqualToString:@"image/jpeg" ]) { imageRef = CGImageCreateWithJPEGDataProvider (dataProvider, NULL , true , kCGRenderingIntentDefault); if (imageRef) { CGColorSpaceRef imageColorSpace = CGImageGetColorSpace (imageRef); CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel (imageColorSpace); if (imageColorSpaceModel == kCGColorSpaceModelCMYK) { CGImageRelease (imageRef); imageRef = NULL ; } } } CGDataProviderRelease (dataProvider); UIImage *image = AFImageWithDataAtScale(data, scale); if (!imageRef) { if (image.images || !image) { return image; } imageRef = CGImageCreateCopy ([image CGImage ]); if (!imageRef) { return nil ; } } size_t width = CGImageGetWidth (imageRef); size_t height = CGImageGetHeight (imageRef); size_t bitsPerComponent = CGImageGetBitsPerComponent (imageRef); if (width * height > 1024 * 1024 || bitsPerComponent > 8 ) { CGImageRelease (imageRef); return image; } size_t bytesPerRow = 0 ; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB (); CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel (colorSpace); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo (imageRef); if (colorSpaceModel == kCGColorSpaceModelRGB) { uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wassign-enum" if (alpha == kCGImageAlphaNone) { bitmapInfo &= ~kCGBitmapAlphaInfoMask; bitmapInfo |= kCGImageAlphaNoneSkipFirst; } else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) { bitmapInfo &= ~kCGBitmapAlphaInfoMask; bitmapInfo |= kCGImageAlphaPremultipliedFirst; } #pragma clang diagnostic pop } CGContextRef context = CGBitmapContextCreate (NULL , width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo); CGColorSpaceRelease (colorSpace); if (!context) { CGImageRelease (imageRef); return image; } CGContextDrawImage (context, CGRectMake (0.0 f, 0.0 f, width, height), imageRef); CGImageRef inflatedImageRef = CGBitmapContextCreateImage (context); CGContextRelease (context); UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation]; CGImageRelease (inflatedImageRef); CGImageRelease (imageRef); return inflatedImage; }
四、AFSecurityPolicy 1,基本简介 AFSecurityPolicy用来验证服务端证书有效性的类,上文已有介绍,可以借助它配置安全策略。主要有三种:
AFSSLPinningModeNone 这个模式本地没有保存证书,只验证服务器证书是不是系统受信任证书列表里的证书签发的。为默认。 AFSSLPinningModePublicKey 这个模式表示本地存有证书,只比对服务器证书和本地证书的Public Key是否一致。 AFSSLPinningModeCertificate 这个模式同样是本地存有证书,比对服务器证书和本地证书的所有内容,完全一致则信任服务器证书。
而AFSecurityPolicy做的工作就是分别对这三种安全策略做相应的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @interface AFSecurityPolicy : NSObject <NSSecureCoding , NSCopying >@property (readonly , nonatomic , assign ) AFSSLPinningMode SSLPinningMode;@property (nonatomic , strong , nullable ) NSSet <NSData *> *pinnedCertificates;@property (nonatomic , assign ) BOOL allowInvalidCertificates;@property (nonatomic , assign ) BOOL validatesDomainName;+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle; + (instancetype )defaultPolicy; + (instancetype )policyWithPinningMode:(AFSSLPinningMode)pinningMode; + (instancetype )policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates; - (BOOL )evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain; @end
2,AFSecurityPolicy的实现 核心逻辑是evaluateServerTrust方法的实现。默认策略下(AFSSLPinningModeNone),只校验服务端证书的有效合法性:
1 2 3 4 5 6 7 8 9 10 11 static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) { BOOL isValid = NO; SecTrustResultType result; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); #pragma clang diagnostic pop isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); _out: return isValid ; }
其中 kSecTrustResultProceed表示serverTrust验证成功,且该验证得到了用户认可(例如在弹出的是否信任的alert框中选择always trust)。 kSecTrustResultUnspecified表示 serverTrust验证成功,此证书也被暗中信任了,但是用户并没有显示地决定信任该证书。两者取其一就可以认为对serverTrust验证成功。
详细的校验策略如下:
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 - (BOOL )evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { if (domain && self .allowInvalidCertificates && self .validatesDomainName && (self .SSLPinningMode == AFSSLPinningModeNone || [self .pinnedCertificates count] == 0 )) { return NO ; } NSMutableArray *policies = [NSMutableArray array]; if (self .validatesDomainName) { [policies addObject:(__bridge_transfer id )SecPolicyCreateSSL(true , (__bridge CFStringRef )domain)]; } else { [policies addObject:(__bridge_transfer id )SecPolicyCreateBasicX509()]; } SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef )policies); if (self .SSLPinningMode == AFSSLPinningModeNone) { return self .allowInvalidCertificates || AFServerTrustIsValid(serverTrust); } else if (!self .allowInvalidCertificates && !AFServerTrustIsValid(serverTrust)) { return NO ; } switch (self .SSLPinningMode) { case AFSSLPinningModeCertificate: { NSMutableArray *pinnedCertificates = [NSMutableArray array]; for (NSData *certificateData in self .pinnedCertificates) { [pinnedCertificates addObject:(__bridge_transfer id )SecCertificateCreateWithData(NULL , (__bridge CFDataRef )certificateData)]; } SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef )pinnedCertificates); if (!AFServerTrustIsValid(serverTrust)) { return NO ; } NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { if ([self .pinnedCertificates containsObject:trustChainCertificate]) { return YES ; } } return NO ; } case AFSSLPinningModePublicKey: { NSUInteger trustedPublicKeyCount = 0 ; NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); for (id trustChainPublicKey in publicKeys) { for (id pinnedPublicKey in self .pinnedPublicKeys) { if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) { trustedPublicKeyCount += 1 ; } } } return trustedPublicKeyCount > 0 ; } default : return NO ; } return NO ; }
五、AFNetworkReachabilityManager 1,基本使用 一般使用方式是这样,获取单例,设置回调,开始监测:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager]; [manager setReachabilityStatusChangeBlock: ^(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusUnknown: NSLog(@"不明网络" ); break ; case AFNetworkReachabilityStatusNotReachable: NSLog(@"没有网络" ); break ; case AFNetworkReachabilityStatusReachableViaWWAN: NSLog(@"蜂窝网络" ); break ; case AFNetworkReachabilityStatusReachableViaWiFi: NSLog(@"WiFi" ); break ; default: break ; } }]; [manager startMonitoring];
接口比较简单,有这些:
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 @interface AFNetworkReachabilityManager : NSObject @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;+ (instancetype )sharedManager; + (instancetype )manager; + (instancetype )managerForDomain:(NSString *)domain; + (instancetype )managerForAddress:(const void *)address; - (instancetype )initWithReachability:(SCNetworkReachabilityRef )reachability NS_DESIGNATED_INITIALIZER ; - (void )startMonitoring; - (void )stopMonitoring; - (NSString *)localizedNetworkReachabilityStatusString; - (void )setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block; + (instancetype )new NS_UNAVAILABLE ; - (instancetype )init NS_UNAVAILABLE ; @end
2,实现 外界可以指定域名或者套接字地址创建网络状态监听器,默认创建的监听器监测所有域名状态,最终生成SCNetworkReachabilityRef实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 根据域名创建SCNetworkReachabilityRef + (instancetype )managerForDomain:(NSString *)domain { // NSString 转char *, 直接使用UTF8String 方法即可 SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName (kCFAllocatorDefault, [domain UTF8String ]); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; CFRelease(reachability) ; return manager; } // 根据套接字地址创建SCNetworkReachabilityRef + (instancetype )managerForAddress:(const void *)address { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress (kCFAllocatorDefault, (const struct sockaddr *)address); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; CFRelease(reachability) ; return manager; }
当开启网络状态监测时,设置SCNetworkReachabilityRef实例的回调,并创建Runloop,设置Runloop为Common Mode,在回调中处理自己的逻辑。可见,网络状态监测的功能底层是靠SCNetworkReachabilityRef实现的。
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 - (void )startMonitoring { [self stopMonitoring]; if (!self .networkReachability) return ; __weak __typeof (self )weakSelf = self ; AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof (weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } return strongSelf; }; SCNetworkReachabilityContext context = {0 , (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL }; SCNetworkReachabilitySetCallback (self .networkReachability, AFNetworkReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop (self .networkReachability, CFRunLoopGetMain (), kCFRunLoopCommonModes); dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0 ),^{ SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags (self .networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); } }); }
在网络状态改变时,之前注册的回调被调用,首先外界的状态回调networkReachabilityStatusBlock随之被调用,接着在主线程发出网络状态改变的通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusCallback block) { AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags) dispatch_async(dispatch_get_main_queue(), ^{ AFNetworkReachabilityManager *manager = nil if (block) { manager = block(status); } NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter] NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) } [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:manager userInfo:userInfo] }) } static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusCallback)info) }
当停止网络状态监听时,SCNetworkReachabilityRef从Runloop中移除。
1 2 3 4 5 - (void )stopMonitoring { if (!self .networkReachability) return ; SCNetworkReachabilityUnscheduleFromRunLoop (self .networkReachability, CFRunLoopGetMain (), kCFRunLoopCommonModes); }
六、总结 下面是总结出来的有意思的点。
1,AFURLSessionManagerTaskDelegate的职责分明 AFURLSessionManager是NSURLSession及所有的Task的代理,但是如果由它自己处理所有的代理逻辑,不免繁重。作者另建一个AFURLSessionManagerTaskDelegate,专门负责Task的代理工作,把自己接收到的代理消息转发给TaskDelegate,从而职责清晰,逻辑简洁。 我们自己在业务开发中肯定也会遇到这种情况,比如UITableViewDelegate、WKNavigationDelegate等,像这些代理方法都不是个位数,如果完全塞到控制器中,整个逻辑不会特别清晰,这时,新建一个职责单一的代理类来处理这些逻辑就显得非常必要了。
2,异步转同步标准写法 比较常见了。
1 2 3 4 5 6 7 8 9 10 11 __block NSArray *tasks = nil ; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0 ); [self .session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { if ([keyPath isEqualToString:NSStringFromSelector (@selector (dataTasks))]) { tasks = dataTasks; } ... dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return tasks;
3,给任意对象添加附加值 这个附加值就是属性。一般来说,关联属性常用在“给分类添加属性”的场景,实现某个功能的即插即用。但是不局限于此,我们完全可以利用关联属性给直接任意对象添加附加值,比如当产生error时,把error附加到Task上,需要的时候再取出来:
1 2 objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN); error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error;
4,处理证书的质询 绝大多数情况只验证服务器证书是不是系统受信任证书列表里的证书签发的即可,借助AFSecurityPolicy可以这么处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - (void )URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling ; NSURLCredential *credential = nil ; if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust ]) { if ([[AFSecurityPolicy defaultPolicy] evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential ; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge ; } if (completionHandler) { completionHandler(disposition, credential); } }
5,多读单写 栅栏函数+并发队列实现多读单写: (1)可以同时有多个读操作,读操作可以并发执行。 (2)不能同时有多个写操作,写操作串行执行,在写操作之前,读操作都完成。 (3)写操作与读操作、其他写操作是互斥的(读操作的时候,不能有写操作,写操作的时候,不能有读操作)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (NSString *)valueForHTTPHeaderField:(NSString *)field { NSString __block *value; dispatch_sync (self .requestHeaderModificationQueue, ^{ value = [self .mutableHTTPRequestHeaders valueForKey:field]; }); return value; } - (void )setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { dispatch_barrier_sync(self .requestHeaderModificationQueue, ^{ [self .mutableHTTPRequestHeaders setValue:value forKey:field]; }); }
6,迪米特法则 对外暴露方法而非类本身。比如这个接口的设计:
1 2 3 4 5 - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString : (NSString *)URLString parameters:(nullable NSDictionary <NSString *, id> *)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block error:(NSError * _Nullable __autoreleasing *)error;
外界只需要关注formData怎么使用,而不必关注formData是什么、有什么属性。这样做降低了类之间的耦合度,提高了模块的相对独立性。 我们在日常开发的时候也可以借鉴,通过协议告诉外界,这个“NSObject”对象怎么使用。
7,UIImage的线程安全 NSData转UIImage时,使用imageWithData:
方法是非线程安全的,需要加锁。initWithCGImage:
方法是线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + (UIImage *)af_safeImageWithData:(NSData *)data { UIImage * image = nil ; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ imageLock = [[NSLock alloc] init]; }); [imageLock lock]; image = [UIImage imageWithData:data]; [imageLock unlock]; return image; } [[UIImage alloc] initWithCGImage:[image CGImage ] scale:scale orientation:image.imageOrientation];
7,优化UIImage的加载 子线程预解压,当然也可以用AFN现成的方法AFInflatedImageFromResponseWithDataAtScale()
。
1 2 3 4 5 6 7 8 (1 )创建位图上下文 `CGContextRef context = CGBitmapContextCreate (NULL , width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);` (2 )向位图上下文中绘制图片 `CGContextDrawImage (context, CGRectMake (0.0 f, 0.0 f, width, height), imageRef);` (3 )从位图上下文中取出CGImageRef 的图片 `CGImageRef inflatedImageRef = CGBitmapContextCreateImage (context);` (4 )CGImageRef 类型的图片转为UIImage 类型 `UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];`