1.导入库 导入 AFN 的 Podfile:
1 2 3 4 5 source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' target 'TargetName' do pod 'AFNetworking' , '~> 3.0' end
写这篇文章时最新的版本是AFNetworking (3.1.0)。
2.模块划分
模块
作用
NSURLSession
发起请求、上传、下载任务,处理回调数据
Serialization
请求响应的序列化、拼接参数
Reachability
监听网络状态
Security
网络安全策略、证书校验
UIKit
视图+分类,控件中网络资源的下载
3.NSURLSession 1.AFURLSessionManager 网络请求的基类,它维护了一个NSURLSession
对象,主要功能包括:
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 - (instancetype)initWithSessionConfiguration: (NSURLSessionConfiguration * )configuration { 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]; 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 ]; } }]; return self ; }
这里self.operationQueue
是请求回调时所在的队列,而非发起请求时的队列。其最大并发数设置为1,是 NSURLSession 为了保证其回调的顺序,要求其delegateQueue
必须是一个串行队列:
An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
defaultSessionConfiguration的HTTPMaximumConnectionsPerHost
属性默认为4,即每个主机默认同时并发请求数为4个。必要时也可自己提供额外的 OperationManager 控制并发情况。
1 2 3 4 5 6 // 数据任务 - (NSURLSessionDataTask *)dataTaskWithRequest:completionHandler:// 上传任务 - (NSURLSessionUploadTask *)uploadTaskWithRequest:fromFile:progress:completionHandler:// 下载任务 - (NSURLSessionDownloadTask *)downloadTaskWithRequest:progress:destination:completionHandler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #pragma mark - NSURLSessionDataDelegate - (void )URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; [delegate URLSession:session dataTask:dataTask didReceiveData:data]; }#pragma mark - NSURLSessionDownloadDelegate - (void )URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; }
2.AFHTTPSessionManager 继承自AFURLSessionManager
,将父类中的dataTaskWithRequest
进一步细分成 6 大类:
方法
作用
GET
请求指定页面的信息,返回数据实体;
HEAD
与GET类似,不同在于只返回头信息而没有具体数据;
POST
将数据封装在请求体中,上传至指定目录,由服务器创建或更新对应的资源;
PUT
将数据传送给服务器以取代指定文档中的内容;
PATCH
是对PUT方法的补充,用来对已知资源进行局部更新;
DELETE
请求删除指定页面或资源;
对应的6大接口:
1 2 3 4 5 6 7 8 9 10 11 - (NSURLSessionDataTask*)GET :parameters :progress :success :failure : - (NSURLSessionDataTask*)POST :parameters : ...progress: success: failure: - (NSURLSessionDataTask*)PUT :parameters :success :failure : - (NSURLSessionDataTask*)DELETE :parameters :success :failure : - (NSURLSessionDataTask*)HEAD :parameters :success :failure : - (NSURLSessionDataTask*)PATCH :parameters :success :failure :
6 种任务最终通过下面这个统一入口调用父类AFURLSessionManager中的方法发起网络任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:URLString: parameters:uploadProgress:downloadProgress:success:failure:{ NSMutableURLRequest *request = [self.requestSerializer requestWithMethod: method URLString: [[NSURL URLWithString:URLString relativeToURL: self.baseURL] absoluteString] parameters: parameters error: & serializationError]; __block NSURLSessionDataTask *dataTask = nil ; dataTask = [self dataTaskWithRequest:request uploadProgress: uploadProgress downloadProgress: downloadProgress completionHandler: ^(/../) { } ]; return dataTask ; }
AFURLSessionManager中对应的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 - (NSURLSessionDataTask *)dataTaskWithRequest: uploadProgress:downloadProgress: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; }
发起请求的操作被放在一个名为 url_session_manager_create_task_safely 的 block 里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static void url_session_manager_create_task_safely ( dispatch_block_t block) { if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { dispatch_sync (url_session_manager_creation_queue(), block); } else { block(); } } static dispatch_queue_t url_session_manager_creation_queue () { static dispatch_queue_t af_url_session_manager_creation_queue; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ af_url_session_manager_creation_queue = dispatch_queue_create( "com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL); }); return af_url_session_manager_creation_queue; }
这个 block 是用来修复 iOS8 以下创建 NSURLSession 时taskIdentifier
不唯一的问题。
taskIdentifier
在后面会被用作保存 AFURLSessionManagerTaskDelegate 对象的 key。所以存在多个 NSURLSession 时,如果此值不唯一,那么 AFURLSessionManagerTaskDelegate 对象与 dataTask 的映射就会出问题。修复方法就是在iOS8以下使用串行
+同步
的方式发起请求。
3.TaskDelegate 这是一个定义在AFURLSessionManager.m
内部的类,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate , NSURLSessionDataDelegate , NSURLSessionDownloadDelegate >@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;@property (nonatomic , copy ) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;@property (nonatomic , copy ) AFURLSessionTaskProgressBlock uploadProgressBlock;@property (nonatomic , copy ) AFURLSessionTaskProgressBlock downloadProgressBlock;@property (nonatomic , copy ) AFURLSessionTaskCompletionHandler completionHandler;@end
作用:作为工具类处理 NSURLSession 的数据回调:持有返回的数据、监听进度、回到主线程。
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 #pragma mark - NSURLSessionDataTaskDelegate - (void )URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self .mutableData appendData:data]; }#pragma mark - NSURLSessionTaskDelegate - (void )URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (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(), ^{ }); }); } }
AFURLSessionManagerTaskDelegate 与 AFURLSessionManager 是如何关联起来的呢?
#1.2
章节中说到,AFURLSessionManager 最终会通过统一的入口调用父类中的方法发起请求。在父类发起网络请求后,紧接着就会调用到以下函数:
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 - (void )addDelegateForDataTask:(NSURLSessionDataTask *)dataTask uploadProgress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(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; } - (void )setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { [self.lock lock ]; self.mutableTaskDelegatesKeyedByTaskIdentifier [@(task.taskIdentifier)] = delegate ; [delegate setupProgressForTask:task ]; [self addNotificationObserverForTask:task ]; [self.lock unlock ]; } - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task { AFURLSessionManagerTaskDelegate *delegate = nil; [self.lock lock ]; delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)]; [self.lock unlock ]; return delegate ; }
所以,AFURLSessionManagerTaskDelegate
是通过 -setDelegate:forTask: 方法,作为 AFURLSessionManager 中mutableTaskDelegatesKeyedByTaskIdentifier
字典属性的 value 被保存起来的,其key 是 NSURLSessionTask 的taskIdentifier
。读取时通过-delegateForTask: 根据此标识符取出 task 对应的代理。
另外,AFURLSessionManagerTaskDelegate 定义里有个属性:AFURLSessionManager *manager,在函数 -addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler: 里这个 manager 被设置为 self。那么这里存在一个引用链:AFURLSessionManager -> mutableTaskDelegatesKeyedByTaskIdentifier -> AFURLSessionManagerTaskDelegate -> AFURLSessionManager。
这里看似有个相互引用问题,AFN的处理是使用weak
声明 manager 属性(见上面的定义),并在请求执行完成后的回调函数里做了如下处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (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]; } } - (void )removeDelegateForTask:(NSURLSessionTask *)task { [self .lock lock]; [self .mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self .lock unlock]; }
AFURLSessionManagerTaskDelegate 对象被从 mutableTaskDelegatesKeyedByTaskIdentifier 字典中移除。那么这个引用链就被打破,也就不存在相互引用问题了。
4.监听进度 TaskDelegate 作为代理
会监听任务的进度。其内部是通过 KVO 监听 NSURLSessionTask 的countOfBytesSent
等字段实现的:
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 - (void )setupProgressForTask:(NSURLSessionTask *)task { [task addObserver:self forKeyPath:NSStringFromSelector (@selector (countOfBytesReceived)) options:NSKeyValueObservingOptionNew context:NULL ]; [task addObserver:self forKeyPath:NSStringFromSelector (@selector (countOfBytesExpectedToReceive)) options:NSKeyValueObservingOptionNew context:NULL ]; [task addObserver:self forKeyPath:NSStringFromSelector (@selector (countOfBytesSent)) options:NSKeyValueObservingOptionNew context:NULL ]; [task addObserver:self forKeyPath:NSStringFromSelector (@selector (countOfBytesExpectedToSend)) options:NSKeyValueObservingOptionNew context:NULL ]; } - (void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary *)change context:(void *)context { if ([object isKindOfClass:[NSURLSessionTask class ]] || [object isKindOfClass:[NSURLSessionDownloadTask class ]]) { if ([keyPath isEqualToString: NSStringFromSelector (@selector (countOfBytesReceived))]) { self .downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey ] longLongValue]; } } else if ([object isEqual:self .downloadProgress]) { if (self .downloadProgressBlock) { self .downloadProgressBlock(object); } } else if ([object isEqual:self .uploadProgress]) { if (self .uploadProgressBlock) { self .uploadProgressBlock(object); } } }
4.Serialization 1.AFURLRequestSerialization 根据用户设置的请求类型,设置 NSURLRequest 的各项参数。
1 2 3 4 5 6 7 @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;
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 - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id )parameters error:(NSError *)error { NSURL *url = [NSURL URLWithString:URLString]; NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; mutableRequest.HTTPMethod = method; mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; return mutableRequest; } - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id )parameters error:(NSError *)error { if (parameters) { if (self .queryStringSerialization) { NSError *serializationError; query = self .queryStringSerialization(request, parameters, &serializationError); } } 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 (![mutableRequest valueForHTTPHeaderField:@"Content-Type" ]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type" ]; } [mutableRequest setHTTPBody: [query dataUsingEncoding:self .stringEncoding]]; } return mutableRequest; }
2.AFHTTPResponseSerializer 校验返回的response
和data
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - (BOOL )validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error { BOOL responseIsValid = YES ; NSError *validationError = nil ; if (response && [response isKindOfClass:[NSHTTPURLResponse class ]]) { if (self .acceptableContentTypes && ![self .acceptableContentTypes containsObject:[response MIMEType]] && !([response MIMEType] == nil && [data length] == 0 )) { if ([data length] > 0 && [response URL]) { responseIsValid = NO ; } responseIsValid = NO ; } if (self .acceptableStatusCodes && ![self .acceptableStatusCodes containsIndex:(NSUInteger )response.statusCode] && [response URL]) { responseIsValid = NO ; } } if (error && !responseIsValid) { *error = validationError; } return responseIsValid; }
5.Reachability 使用 SystemConfiguration.framework 中的 SCNetworkReachability 类,监听网络状态:
1 2 3 4 @property (readonly , nonatomic , assign ) AFNetworkReachabilityStatus networkReachabilityStatus;
网络状况的枚举定义如下:
1 2 3 4 5 6 7 8 9 typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { AFNetworkReachabilityStatusUnknown = -1 ,// 未知 AFNetworkReachabilityStatusNotReachable = 0 , // 未联网 AFNetworkReachabilityStatusReachableViaWWAN = 1 , // 蜂窝网络 AFNetworkReachabilityStatusReachableViaWiFi = 2 , // WIFI AFNetworkReachabilityStatusReachableVia2G = 3 , // 2 G AFNetworkReachabilityStatusReachableVia3G = 4 , // 3 G AFNetworkReachabilityStatusReachableVia4G = 5 , // 4 G };
可在 AppDelegate 中开启网络状态的监听:
1 2 3 4 5 6 7 8 9 10 11 12 - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { AFNetworkReachabilityManager *shareMana = [AFNetworkReachabilityManager sharedManager]; [shareMana startMonitoring]; [shareMana setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { dispatch_async (dispatch_get_main_queue(), ^{ }); }]; return YES ; }
6.Security 处理与服务器交互时的验证问题,保证应用与服务器通信时的SSL连接安全。
客户端与服务器进行通信时,服务器会返回一个SSL证书,客户端需要验证证书的颁发机构、公钥、是否过期等信息。SSL Pinning 就是iOS中用来处理此类事务的,它可以按照你的设置,将服务器下发的证书与本地保存的证书进行对比,验证通过才能继续通信。这些设置在 AFN 中分三类:
验证模式
作用
AFSSLPinningModeNone
客户端无条件地信任服务器端返回的证书,不做校验;
AFSSLPinningModePublicKey
只验证服务器端返回的证书中公钥的部分,与本地证书一致才继续;有效期等不做校验(省去过期的麻烦);
AFSSLPinningModeCertificate
验证服务器端返回的证书和本地保存的证书中的所有内容,有一项不符合则验证失败;
AFN 会根据用户设置的验证模式处理 SSL Pinning 验证业务。其中后两种验证模式需要将服务器的证书文件拷贝一份保存到工程目录中。
AFN的 AFHTTPSessionManager 类中有个securityPolicy
属性:
1 2 3 4 5 @property (nonatomic , strong ) AFSecurityPolicy *securityPolicy;
一般是在基于 AFHTTPSessionManager 的单例类中给 securityPolicy 属性指定一个验证模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @interface TLHttpAPIClient : AFHTTPSessionManager @end @implementation TLHttpAPIClient + (instancetype )sharedClient { static TLHttpAPIClient *sharedClient = nil ; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ sharedClient = [TLHttpAPIClient new]; AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode: AFSSLPinningModeCertificate]; NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"Demo" ofType:@"cer" ]; NSData *cerData = [NSData dataWithContentsOfFile:cerPath]; [securityPolicy setPinnedCertificates:@[cerData]]; sharedClient.securityPolicy = securityPolicy; }); return sharedClient; }@end
AFSSLPinningModeNone模式下不用设置证书路径。使用证书进行验证时,注意证书是否过期。
7.小结 源码部分暂时先写到这,UIKit+AFNetworking 部分及其他细节待续~~说说目前对AFN的感受吧。
各模块分工清晰,职责明确,高聚合、低耦合;
请求被封装成6个数据接口和上传下载接口,方便地应对各种场景;
大量使用了异步、block、C函数;
多翻翻 AFN 在Github上的 issue 和解决方案,看人家是怎么解决问题的,会有很多收获~