<简书 — 刘小壮>git
AFNetworking
是iOS
最经常使用的网络框架,虽然系统也有NSURLSession
,可是咱们通常不会直接用它。AFNetworking
通过了三个大版本,如今用的大多数都是3.x的版本。github
AFNetworking
经历了下面三个阶段的发展:json
NSURLConnection
的封装。NSURLConnection
和NSURLSession
,是转向NSURLSession
的过渡版。NSURLSession
的封装。AFNetworking3.X
的构成很简单,主要就四部分,除此以外还有一些基于UIKit
的Category
,但这些并非标配。数组
Manager
,主要实现都在AFURLSessionManager
中。HTTPS
相关的。在AFN3.0
中,网络请求的manager
主要有AFHTTPSessionManager
和AFURLSessionManager
构成,两者为父子关系。这两个类职责划分很清晰,父类负责处理一些基础的网络请求代码,而且接受NSURLRequest
对象,而子类则负责处理和http
协议有关的逻辑。缓存
AFN
的这套设计很便于扩展,若是之后想增长FTP
协议的处理,则基于AFURLSessionManager
建立子类便可。子类中只须要进行不多的代码处理,建立一个NSURLRequest
对象后调用父类代码,由父类去完成具体的请求操做。安全
AFHTTPSessionManager
类的初始化方法中并无太多实现代码,其内部调用的都是父类AFURLSessionManager
的initWithSessionConfiguration
方法,下面是此方法内部的一些关键代码。服务器
在初始化方法中包含一个参数sessionConfiguration
,若是没有传入的话默认是使用系统的defaultConfiguration
,咱们建立是通常都不会自定义configuration
,因此大多数都是系统的。网络
if (!configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; }
随后是NSURLSession
的初始化代码,关于NSOperationQueue
后面详细进行讲解。NSURLSession
的初始化方式有两种,一种是使用系统的共享session
,另外一种是本身建立session
。AFN
选择的是建立本身的session
,而且每一个请求都会建立一个独立的session
。session
能够经过NSURLSession
进行链接复用,这样能够避免不少握手和挥手的过程,提升网络请求速度,苹果容许iOS
设备上一个域名能够有四个链接同时存在。可是因为AFN
的实现是每一个请求都建立一个session
,因此就不能进行链接复用。多线程
因此能够经过在外面对AFN
进行二次封装,将AFHTTPSessionManager
复用为单例对象,经过复用sessionManager
的方式,来进行链接的复用。可是这种方案对于不一样的requestSerializer
、responseSerializer
等状况,仍是要作特殊兼容,因此最好创建一个sessionManager
池,对于同类型的sessionManager
直接拿出来复用,不然就建立新的。
// 共享session链接池 [NSURLSession sharedSession]; // 建立新session,则不能使用共享session链接池 [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
因为当前AFURLSessionManager
对象的全部sessionTask
请求任务,都是共享同一个回调代理的,因此AFN
为了区分每一个sessionTask
,经过下面的可变字典,将全部taskDelegate
和task.taskIdentifier
的进行了一一对应,以便于很容易的对每一个请求task
进行操做。
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
在初始化方法中,能够发现AFN
在建立session
后,调用了getTasksWithCompletionHandler
方法来获取当前全部的task
。可是如今刚建立session
,理论上来讲是不该该有task
的。但从AFN
的issues
中找到了答案issues 3499。
这是由于,在completionHandler
回调中,为了防止进入前台时,经过session id
恢复的task
致使一些崩溃问题,因此这里将以前的task
进行遍历,并将回调都置nil
。
[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]; } }];
在AFURLSessionManager
中进行task
的建立,task
的类型总共分为三种,dataTask
、uploadTask
、downloadTask
,AFN
并无对streamTask
进行处理。
AFHTTPSessionManager
在建立GET
、POST
等请求时,本质上都是调用了下面的方法或其相似的方法,方法内部会建立一个task
对象,并调用addDelegateForDataTask
将后面的处理交给AFURLSessionManagerTaskDelegate
来完成。随后会将task
返回给调用方,调用方获取到task
对象后,也就是子类AFHTTPSessionManager
,会调用resume
方法开始请求。
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler { __block NSURLSessionDataTask *dataTask = nil; url_session_manager_create_task_safely(^{ dataTask = [self.session dataTaskWithRequest:request]; }); [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; return dataTask; }
除了普通请求外,upload
、download
都有相似的处理。
在addDelegateForDataTask
方法中,会调用sessionManager
的setDelegate:forTask:
方法,此方法内部将task
和taskDelegate
进行了注册。因为AFN
能够经过通知让外界监听请求状态,因此在此方法中还监听了task
的resume
和suspend
事件,并在实现代码中将事件广播出去。
- (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; }
若是从AFHTTPSessionManager
的建立任务开始,按代码逻辑跟到这里,发现其实AFN3.0
的请求代码真的很简单,主要都集中在建立NSMutableURLRequest
那里,其余都依赖于NSURLSession
,由于确实NSURLSession
的API
封装程度比较好,也很好使用。
AFN3.0
的做用就是对NSURLSession
的封装性比较好,你不用去写太多重复性的代码,而且能够很容易的经过block
获得回调结果。
NSURLSession
的回调方法比较多,这里只针对一些关键代码进行讲解,以及梳理总体回调逻辑,不一一列举每一个回调方法的做用,详细源码各位能够直接下载AFN
代码查看。
在AFURLSessionManager
中,有一个AFURLSessionManagerTaskDelegate
类比较重要,这个类和sessionTask
是一一对应的,负责处理sessionTask
请求的不少逻辑,NSURLSessionDelegate
的回调基本都转发给taskDelegate
去处理了。在NSURLSession
回调中处理了HTTPS
证书验证、下载进度之类的,没有太复杂的处理。
taskDelegate
的设计很不错,能够将代理回调任务处理对象化,也能够给AFURLSessionManager
类瘦身。比较理想的是直接将代理设置为taskDelegate
,可是因为会涉及一些AFURLSessionManager
自身的处理逻辑,因此才设计为消息传递的方式。
taskDelegate
的功能很简单,主要是NSData
数据的处理,NSProgress
上传下载进度的处理,以及通知参数的处理。在进行AFN
的下载处理时,NSData
的数据拼接、事件回调,及文件处理,都是由taskDelegate
来完成的。
下面是downloadTask
任务完成时的处理代码,其余回调代码就不一一列举了。
- (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]; } } } }
taskDelegate
中有一个很好的设计,taskDelegate
并不直接在NSURLSession
的代理方法中作进度拼接和回调。而是对于上传和下载任务分别对应不一样的NSProgress
,并经过KVO
来监听fractionCompleted
属性,而且实现cancel
、suspend
等状态回调。任务的状态和进度处理交给NSProgress
,在回调方法中直接拼接NSProgress
的进度,从而回调KVO
方法。
NSProgress
内部的cancel
、pause
、resume
方法,正好能够对应到sessionTask
的方法调用。可是从代码角度来看,AFN
好像并无进行相关的调用,但这个设计思路很好。
_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(iOS 9, 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]; }
看过源码的话,能够发现AFURLSessionManager
中还有一个_AFURLSessionTaskSwizzling
类,这里咱们简称taskSwizzling
类。我认为此类的设计实在是冗余,此类的主要功能就是在+load
方法中进行一个swizzling
,将dataTask
的resume
和suspend
方法进行替换,而且在替换后的方法中发出对应的通知,并无太多实际的功能。
只不过taskSwizzling
类中仍是有一些不错的代码设计值得借鉴的,因为sessionTask
存在一系列继承链,因此直接对其进行swizzling
对其余子类并不生效,由于每一个子类都有本身的实现,而写一大堆swizzling
又没有什么技术含量。
在iOS7
和iOS8
上,sessionTask
的继承关系并不同,最好进行一个统一的处理。AFN
采起的方式是建立一个dataTask
对象,并对这个对象进行swizzling
,而且遍历其继承链一直进行swizzling
,这样保证集成继承链的正确性。
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration]; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnonnull" NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil]; #pragma clang diagnostic pop IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume))); Class currentClass = [localDataTask class]; while (class_getInstanceMethod(currentClass, @selector(resume))) { Class superClass = [currentClass superclass]; IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume))); IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume))); if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) { [self swizzleResumeAndSuspendMethodForClass:currentClass]; } currentClass = [currentClass superclass]; } + (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass { Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume)); Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend)); if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) { af_swizzleSelector(theClass, @selector(resume), @selector(af_resume)); } if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) { af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend)); } }
AFN
为了不发生编译器警告,采起了预编译指令对代码进行修饰,预编译指令基本由三部分组成,push
、pop
、ignored
类型。Github
上有人维护了一份clang warning清单,若是想进行对应的预编译处理能够上去找找有没有合适的。
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" #pragma clang diagnostic pop
NSURLSession
在iOS8
如下会并发建立多个task
,但并发设置task identifier
的时候会存在identifier
重复的问题。为了解决这个问题,在iOS8
如下,系统将全部sessionTask
的建立都放在一个同步的串行队列中进行,保证建立及赋值操做是串行进行的。
url_session_manager_create_task_safely(^{ dataTask = [self.session dataTaskWithRequest:request]; }); url_session_manager_create_task_safely(^{ uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData]; }); // 若是Foundation版本小于iOS8,则把block任务放在一个同步队列中执行。这个问题是因为在iOS8如下并发建立任务,可能会有多个相同的identifier 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(); } }
一个比较有意思的是,AFN
为了让开发者明白为何要加这个判断,对iOS8
系统的判判定义成了一个宏,而且用Apple Support
的id
做为宏定义命名,很见名知意。
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
AFN
在回调didCompleteWithError
方法,并处理返回数据时,会切换到其余线程和group
去处理,处理完成后再切换到主线程并通知调用方。
AFN
提供了两个属性,用来设置请求结束后进行回调的dispatch queue
和dispatch group
,若是不设置的话,AFN
会有默认的实现来处理请求结束的操做。下面是group
和queue
的实现,AFN
对于返回数据的处理,采用的是并发处理。
static dispatch_queue_t url_session_manager_processing_queue() { static dispatch_queue_t af_url_session_manager_processing_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT); }); return af_url_session_manager_processing_queue; } static dispatch_group_t url_session_manager_completion_group() { static dispatch_group_t af_url_session_manager_completion_group; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_completion_group = dispatch_group_create(); }); return af_url_session_manager_completion_group; }
AFN
在建立AFURLSessionManager
的operationQueue
时,将其最大并发数设置为1。这是由于在建立NSURLSSession
时,苹果要求网络请求回来的数据顺序执行,为了保证代理方法的执行顺序,因此须要串行的调用NSURLSSession
的代理方法。
AFHTTPSessionManager
本质上是对父类AFURLSessionManager
的封装,主要实现都在父类中,本身内部代码实现很简单。在建立AFHTTPSessionManager
时会传入一个baseURL
,以及指定requestSerializer
、responseSerializer
对象。
从代码实现来看,AFN
的请求并非单例形式的,每一个请求都会建立一个新的请求对象。平时调用的GET
、POST
等网络请求方法,都定义在AFHTTPSessionManager
中。AFHTTPSessionManager
内部则调用父类方法,发起响应的请求并获取到task
对象,调用task
的resume
后返回给调用方。
- (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters progress:(void (^)(NSProgress * _Nonnull))downloadProgress success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure]; [dataTask resume]; return dataTask; }
AFURLRequestSerialization
负责建立NSMutableURLRequest
请求对象,并对request
进行请求的参数拼接、设置缓存策略、设置请求头等关于请求相关的配置。
AFURLRequestSerialization
并非一个类,而是一个文件,其中包含三个requestSerializer
请求对象,分别对应着不一样的请求序列化器。
JSON
请求。xml
格式请求。这三个类区别就在于Content-Type
不一样,其余基本都是同样的。AFN
默认是HTTP
的。
在文件中定义了同名的AFURLRequestSerialization
协议,不一样的requestSerializer
会对协议方法有不一样的实现,下面是AFHTTPRequestSerializer
的实现代码。其核心代码实现也比较直观,就是在建立requestSerializer
的时候,设置请求头的公共参数,以及将请求参数经过NSJSONSerialization
转换为NSData
,并将其赋值给request
对象的httpBody
,下面是精简后的核心代码。
- (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]; } }]; if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; } NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]; [mutableRequest setHTTPBody:jsonData]; } return mutableRequest; }
若是想给网络请求设置请求参数的话,须要经过requestSerializer
对外暴露的API
添加参数,AFN
的requestManager
并不直接对外提供设置请求头的代码。经过requestSerializer
能够对请求头进行添加和删除、以及清空的操做。
从建立AFURLRequestSerialization
对象到最后返回NSURLRequest
对象,中间的过程并不复杂,主要是设置请求头和拼接参数,逻辑很清晰。
AFURLRequestSerialization
有一个很重要的功能就是参数处理,AFQueryStringPair
就是负责处理这些参数的。pair
类中定义了两个属性,分别对应请求参数的key
、value
。除此以外,还定义了一些很是实用的C语言函数。
@interface AFQueryStringPair : NSObject @property (readwrite, nonatomic, strong) id field; @property (readwrite, nonatomic, strong) id value; - (id)initWithField:(id)field value:(id)value; - (NSString *)URLEncodedStringValue; @end
AFQueryStringFromParameters
函数负责将请求参数字典,转成拼接在URL
后面的参数字符串,这个函数是AFQueryStringPair
类中定义的一个关键函数。函数内部经过AFQueryStringPairsFromDictionary
函数将参数字典,转为存储pair
对象的数组并进行遍历,遍历后调用URLEncodedStringValue
方法对参数进行拼接,最后成为字符串参数。
URLEncodedStringValue
方法实现很简单,就是进行一个key
、value
的拼接,而且在中间加上“=”。
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray array]; for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValue]]; } return [mutablePairs componentsJoinedByString:@"&"]; } - (NSString *)URLEncodedStringValue { if (!self.value || [self.value isEqual:[NSNull null]]) { return AFPercentEscapedStringFromString([self.field description]); } else { return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]; } }
下面是参数拼接的代码,函数内部会将原有的参数,转换为AFQueryStringPair
对象的类型,但以前的层级结构不变。这句话是什么意思呢,就是说对原有传入的对象进行逐层递归调用,而且将最后一层字典的key
、value
参数,转成pair
类型的对象,而且将嵌套有pair
对象的数组返回给调用方。
对象层级不变,但字典、集合都会被转换为数组结构,也就是以前传入字典、数组、字典的嵌套结构,返回的时候就是数组、数组、pair
的结构返回。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) { return AFQueryStringPairsFromKeyAndValue(nil, dictionary); } NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; 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]]; } return mutableQueryStringComponents; }
AFHTTPRequestSerializer
在建立NSMutableURLRequest
时,须要为request
设置属性。serializer
对外提供了和request
同名的一些属性,外界直接调用serializer
便可设置request
的属性。
AFHTTPRequestSerializer
内部建立request
时,并非根据设置request
的属性按个赋值,而是经过一个属性数组AFHTTPRequestSerializerObservedKeyPaths
,将serializer
须要赋值给request
的属性,都放在数组中。
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() { static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; }); return _AFHTTPRequestSerializerObservedKeyPaths; }
在初始化AFHTTPRequestSerializer
时,遍历keyPath
数组并经过KVO
的方式,监听serializer
的赋值。若是外界对serializer
对应的属性进行赋值,则将其添加到mutableObservedChangedKeyPaths
数组中。在建立request
对象是,遍历mutableObservedChangedKeyPaths
数组并将值赋值给request
对象。
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } } - (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]; } } } for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } }
当进行POST
表单提交时,须要用到AFMultipartFormData
协议。调用POST
方法后,会回调一个遵照此协议的对象,能够经过此对象进行表单提交操做。
[manager POST:requestURL parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { [formData appendPartWithFileData:params[@"front_img"] name:@"front_img" fileName:frontImgfileName mimeType:@"multipart/form-data"]; [formData appendPartWithFileData:params[@"reverse_img"] name:@"reverse_img" fileName:reverseImgfileName mimeType:@"multipart/form-data"]; [formData appendPartWithFileData:params[@"face_img"] name:@"face_img" fileName:faceImgfileName mimeType:@"multipart/form-data"]; } progress:^(NSProgress * _Nonnull uploadProgress) { // nothing } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { // nothing } failure:nil];
进行表单提交时,能够直接传入文件,也能够传入路径。表单提交能够同时提交多个文件,理论上数量不受限制。
AFN
的缓存策略和NSURLCache
的缓存策略一致,而且直接使用系统的枚举,这对iOS
开发者是很是友好的。下面是枚举定义,忽略掉一些unimplemented
的,和一些重定向到已有枚举的,可用的都在这。
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy) { NSURLRequestUseProtocolCachePolicy = 0, NSURLRequestReloadIgnoringLocalCacheData = 1, NSURLRequestReturnCacheDataElseLoad = 2, NSURLRequestReturnCacheDataDontLoad = 3, NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, NSURLRequestReloadRevalidatingCacheData = 5, };
AFURLResponseSerialization
负责处理response
相关的逻辑,其功能主要是设置acceptType
、编码格式和处理服务器返回数据。一样的,AFURLResponseSerialization
也有同名的协议,每一个子类都遵循代理方法并实现不一样的返回值处理代码。
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error;
和AFURLRequestSerialization
同样,AFURLResponseSerialization
由一个父类和六个子类构成,子类中有一个是Mac
的,因此这里不作分析,子类的职责只是对acceptType
作修改以及处理具体的返回数据。
NSData
二进制。JSON
返回数据,也是默认类型。XML
返回数据,由系统NSXMLParser
负责处理。XML
返回数据,也就是plist
数据。因为服务器有时候会返回null
的状况,系统会将其转换为NSNull
对象,而对NSNull
对象发送不正确的消息,就会致使崩溃。从服务器接收到返回值后,AFN
会对返回值进行一个递归查找,找到全部NSNull
对象并将其移除,防止出现向NSNull
对象发送消息致使的崩溃。
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) { if ([JSONObject isKindOfClass:[NSArray class]]) { NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]]; for (id value in (NSArray *)JSONObject) { [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)]; } return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray]; } else if ([JSONObject isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject]; for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) { id value = (NSDictionary *)JSONObject[key]; if (!value || [value isEqual:[NSNull null]]) { [mutableDictionary removeObjectForKey:key]; } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions); } } return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary]; } return JSONObject; }
在使用NSBundle
对象时,咱们最经常使用的就是mainBundle
或者bundleWithPath
这种方式获取bundle
,这种对于都是从app
二进制读取的时候是没有问题的。可是若是涉及到framework
动态库,就不是那么易于使用。
framework
中能够包含资源文件,例如.bundle
文件。若是是动态库形式的framework
(framework
也有静态形式),其会以一个独立二进制的形式表现,而且会分配独立的二进制空间。在读取bundle
的时候,就能够考虑使用bundleForClass
的方式读取。
bundleForClass
表示从当前类定义的二进制,所在的程序包中读取NSBundle
文件。例如.app
就是从main bundle
中读取,若是是framework
就从其所在的二进制中读取。
AFN
提供了一些UIKit
的Category
,例如网络请求发起时,网络指示器转菊花,则由AFNetworkActivityIndicatorManager
类负责。开启网络指示器很简单,添加下面代码便可,网络指示器默认是关闭的。
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
这里不对AFNetworkActivityIndicatorManager
的代码进行过多的分析,只是调其中比较重要的点来分析,下面统称为indicatorManager
。
以前在_AFURLSessionTaskSwizzling
类中写了不少代码,就是为了发出resume
和suspend
两个通知,这两个通知在indicatorManager
中就用到了。网络指示器监听了下面的三个通知,而且彻底由通知来驱动。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
若是看indicatorManager
中的源码,你会发现为何里面还有timer
,彻底不须要啊,有网络请求就转菊花,没网络请求就中止不就好了吗?
这是由于AFN
考虑,若是一个网络请求很快的话,会致使菊花出现转一下很快就消失的状况,若是网络请求比较多会屡次闪现。因此对于这个问题,indicatorManager
经过Timer
的方式实现,若是在指定的区间内网络请求已经结束,则不在显示菊花,若是有屡次请求则在请求之间也不进行中断。
对于开始转圈设置的是1.0秒,结束转圈设置的是0.17秒。也就是当菊花开始旋转时,须要有1.0秒的延时,这个时间足以保证以前的菊花中止转动。结束转圈则会在0.17秒以后进行,能够保证菊花的旋转至少会有0.17秒。
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0; static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17; - (void)startActivationDelayTimer { self.activationDelayTimer = [NSTimer timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes]; } - (void)startCompletionDelayTimer { [self.completionDelayTimer invalidate]; self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes]; }
因为indicatorManager
是采用通知的方式进行回调,全部的网络请求通知都会调到这。因此当多个网络请求到来时,会经过一个_activityCount
来进行计数,能够将其理解为一个队列,这样更容易理解。在显示网络指示器的时候,就是基于_activityCount
来进行判断的,若是队列中有请求则显示网络指示器,不管有多少请求。
这种设计思路比较好,在项目中不少地方均可以用到。例若有些方法须要成对进行调用,例如播放开始和暂停,若是某一个方法调用屡次就会形成bug
。这种方式就比较适合用count
的方式进行容错,内部针对count
作一些判断操做。
- (void)incrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { _activityCount++; } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; }); } - (void)decrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { _activityCount = MAX(_activityCount - 1, 0); } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; }); }
indicatorManager
是多线程安全的,在一些关键地方都经过synchronized
的方式加锁,防止从各个线程调用过来的通知形成资源抢夺的问题。
AFN
支持https
请求,并经过AFSecurityPolicy
类来处理https
证书及验证,但其https
请求的执行仍是交给NSURLSession
去完成的。
下面是NSURLSession
的一个代理方法,当须要进行证书验证时,能够重写此方法并进行自定义的验证处理。验证完成后经过completionHandler
的block
来告知处理结果,而且将验证结果disposition
和公钥credential
传入。
AFN
经过AFSecurityPolicy
类提供了验证逻辑,而且在内部能够进行证书的管理。也能够不使用AFN
提供的验证逻辑,重写sessionDidReceiveAuthenticationChallenge
的block
便可自定义验证逻辑,不走AFN
的逻辑。
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.sessionDidReceiveAuthenticationChallenge) { disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if (credential) { disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); } }
除了进行NSURLSession
请求验证的回调,对于每一个task
也有对应的代理方法。两个代理方法内部实现基本同样,区别在于对于每一个task
,AFN
提供了taskDidReceiveAuthenticationChallenge
回调block
,能够由外界自定义证书验证过程。
验证结果是经过一个枚举回调给NSURLSession
的,参数是一个NSURLSessionAuthChallengeDisposition
类型的枚举,表示证书验证的状况,此枚举包含下面几个具体值。
使用当前证书创建SSL
链接,并处理后续请求
使用默认的处理方式,当前证书被忽略
验证不经过,取消整个网络请求
此次验证被忽略,但不取消网络请求
HTTPS
请求的密钥管理等安全相关的处理,都放在Security.framework
框架中。在AFSecurityPolicy
中常常能够看到SecTrustRef
类型的变量,其表示的就是密钥对象,其中包含了公钥等信息。
咱们能够经过下面的命令获取到公钥,具体格式这里不作过多介绍,详细的能够Google一下公钥格式。
// 获取公钥命令 SecTrustCopyPublicKey(serverTrust) // 打印的公钥(公钥已作脱敏) <SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: A51E89C5FFF2748A6F70C4D701D29A39723C3BE495CABC5487B05023D957CD287839F6B5E53F90B963438417547A369BBA5818D018B0E98F2449442DFBD7F405E18D5A827B6F6F0A5A5E5585C6C0F342DDE727681902021B7A7CE0947EFCFDDC4CCF8E100D94A454156FD3E457F4719E3C6B9E408CD4316B976A6C44BD91A057FEA4A115BEB1FE28E71005D2198E3B79B8942779B434A0B08F82B3D390A7B94B958BB71D9B69564C84A1B03FE22D4C4E24BBA2D5ED3F660F1EBC757EF0E52A7CF13A8C167B0E6171B2CD6678CC02EAF1E59147F53B3671C33107D9ED5238CBE33DB617931FFB44DE70043B2A618D8F43608D6F494360FFDEE83AD1DCE120D6F1, addr: 0x280396dc0>
AFSecurityPolicy
的职责比较单一,只处理公钥和验证的逻辑,其定义是一个单例对象。此类主要由四个属性和一个方法构成。
// 证书验证方式 @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; // 本地自签名证书集合 @property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates; // 是否验证证书的合法性(是否容许自签名证书) @property (nonatomic, assign) BOOL allowInvalidCertificates; // 是否验证域名是否有效 @property (nonatomic, assign) BOOL validatesDomainName;
若是进行细分的话,AFSecurityPolicy
的功能基本就两个。一个是经过CA
的方式进行验证,另外一个就是进行SSL Pinning
自签名验证。evaluateServerTrust:forDomain:
是AFSecurityPolicy
最主要的方法,用来进行证书的合法性验证。
AFSecurityPolicy
进行SSL Pinning
验证的方式分为如下三种,若是是None
则会执行正常CA
验证的流程,其余两种都是自签名的流程。AFN
中默认的调用是defaultPolicy
方法,其内部设置的是AFSSLPinningModeNone
模式。
正常流程,经过CA
机构颁发的公钥,对服务器下发的证书验证数字签名,而且得到公钥。
不经过CA
的流程进行验证,而是经过本地内置的服务端证书进行验证,验证过程分为两步。首先验证证书是否过时或失效,其次验证本地是否包含此证书。
不进行CA
的验证,也不验证证书,只验证公钥是否有效。
对于本地自签名证书的管理有两种方式,一种是默认会在本地查找遍历全部.cer
的文件,并存在一个自签名证书的集合中。也能够在建立AFSecurityPolicy
对象时传入SSLPinningMode
,下面是查找本地.cer
文件的逻辑。
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle { NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]]; for (NSString *path in paths) { NSData *certificateData = [NSData dataWithContentsOfFile:path]; [certificates addObject:certificateData]; } return [NSSet setWithSet:certificates]; }
HTTPS
在进行握手时,须要经过CA
的公钥进行验证,保证服务器公钥的合法性,没有被篡改成有问题的公钥。若是使用CA
机构颁发的证书,不管使用NSURLSession
仍是AFNetworking
都不须要修改代码,这些都会自动完成。若是不想使用CA
的证书验证,例如自签名证书在CA
证书验证时就会失败。
这种状况可使用自签名的方式进行验证,也就是在客户端本地内置一份证书,服务器进行四次握手时,经过保存在本地的证书和服务器进行对比,证书相同则表示验证成功,不走CA
的验证方式。
AFN
为咱们提供了自签名证书的验证方法,经过SSLPinningMode
设置验证方式为自签名,而且传入证书集合。若是没有传入证书集合,则AFN
默认会遍历整个沙盒,查找全部.cer
的证书。
进行沙盒验证时,须要将AFSecurityPolicy
的allowInvalidCertificates
设置为YES
,默认是NO
,表示容许无效的证书,也就是自签名的证书。
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"]; NSData *certData = [NSData dataWithContentsOfFile:cerPath]; NSSet *set = [NSSet setWithObject:certData]; AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set]; securityPolicy.allowInvalidCertificates = YES;
AFNetworking2.x
由NSURLSession
和NSURLConnection
两部分组成,而且分别对应不一样的类,这里主要介绍NSURLConnection
部分的源码实现。
NSURLConnection
实现由下面三个类构成,从源码构成能够看出,不管是session
仍是connection
方案,都具备很好的扩展性。例如这里AFHTTPRequestOperation
是基于AFURLConnectionOperation
实现的,若是须要实现FTP
协议,则能够建立一个继承自AFURLConnectionOperation
的AFFPTConnectionOperation
类并重写对应方法便可。
NSOperation
,负责网络请求的逻辑实现,每一个网络请求就是一个Operation
对象。AFURLConnectionOperation
,处理HTTP
相关网络请求。NSOperationQueue
,负责管理全部Operation
网络请求。下面是AFURLConnectionOperation
的初始化方法,和AFURLSessionManager
有些不同。其内部增长了状态的概念,以及RunloopMode
的概念,这两个咱们后面会详细讲解。shouldUseCredentialStorage
表示是否由系统作证书验证,后面设置了securityPolicy
,和sessionManager
同样也是使用默认方案。
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest { _state = AFOperationReadyState; self.lock = [[NSRecursiveLock alloc] init]; self.lock.name = kAFNetworkingLockName; self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; self.request = urlRequest; self.shouldUseCredentialStorage = YES; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; }
AFURLConnectionOperation
继承自NSOperation
,因为NSOperation
和网络请求的过程很像,有开始、暂停、完成等,而且很好的支持KVO
监听,因此AFN
将每一个网络请求都当作一个Operation
任务。AFURLConnectionOperation
能够设置任务优先级,也能够经过AFHTTPRequestOperationManager
设置最大并发数,基本上NSOperationQueue
提供的功能都能用。
在AFHTTPRequestOperationManager
中定义了NSOperationQueue
,建立网络请求任务后,都会被加入到queue
中,随后由系统调用queue
中的operation
任务,执行operation
的start
方法发起请求。AFURLConnectionOperation
只须要在内部实现start
、pause
、resume
等父类方法便可,其余都由系统去进行调用。这种设计能够很好的将manager
和operation
进行解耦,两者不用直接发生调用关系。
NSURLConnection
中,每一个网络请求对应一个AFHTTPRequestOperation
,全部网络请求都共用一个manager
来管理operation
。而AFHTTPSessionManager
则不一样,每一个网络请求对应一个manager
以及一个task
。
AFURLConnectionOperation
支持KVO
的方式,让外界监听网络请求的变化,并经过重写setState
方法,在内部加入willChangeValueForKey
触发KVO
回调。AFN
经过AFOperationState
来管理网络请求状态,下面是AFN
对其的状态定义。
当网络请求状态发生改变时,都会调用setState
方法进行赋值,例以下面是请求完成时的处理代码。除此以外,当判断AFN
请求状态时,也是经过这个属性做为判断依据的。
- (void)finish { [self.lock lock]; self.state = AFOperationFinishedState; [self.lock unlock]; }
AFURLConnectionOperation
中设计了常驻线程,而且重写了operation
的start
等方法,网络请求的start
、cancel
、pause
等操做,都是在常驻线程中完成的。网络请求结束后,数据回调的处理也是在这个线程中完成的。
这是由于在哪一个线程建立NSURLConnection
对象并发出请求,则数据返回时也默认从那个线程接受数据。若是请求都是从主线程发出的,请求返回时若是屏幕正在滑动,runloopMode
为UITrackingRunLoopMode
则不能处理返回数据。而若是把网络请求都加到主线程的NSRunLoopCommonModes
中,在大量网络请求返回时,处理返回数据会影响屏幕滑动FPS
。
因此为了保证网络请求数据能够正常返回并被处理,而又不影响屏幕FPS
,则用一个单独的线程来处理。若是每一个请求都对应一个线程来处理返回任务,会形成大量线程的占用,因此用一个常驻线程来处理全部网络请求,来保证线程资源的最小占用。常驻线程其实是一个单例线程,而且这个单例线程被加入了一个Port
进行保活,保证线程能够不被退出。
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; }
经过AFURLConnectionOperation
发起网络请求时,实际建立connection
对象的代码在下面的方法中。在建立connection
对象后,并无当即发出网络请求,而是将startImmediately
设置为NO
。随后会设置NSURLConnection
和NSOutputStream
的RunloopMode
,网络请求会从单例线程的runLoopModes
中发出,这样当网络请求返回时,回调代码也会在runLoopModes
中去执行。
operationDidStart
方法中会调用NSURLConnection
的scheduleInRunLoop:forMode:
方法,将网络请求任务派发到Runloop
指定的Mode
中。我以为给Operation
设置runLoopModes
其实意义不大,由于常驻线程基本上只会有一个Mode
,也就是NSRunloopDefaultMode
,基本上不会有其余Mode
,因此这里设置runLoopModes
没什么意义。
- (void)operationDidStart { self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; for (NSString *runLoopMode in self.runLoopModes) { [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; } [self.outputStream open]; [self.connection start]; }
AFURLConnectionOperation
是经过NSURLConnection
实现网络请求的,这里简单讲一下operation
中代理方法的实现。
AFN
实现了https
证书验证的代码,具体实现和AFURLSessionManager
基本相似,而且也是经过AFSecurityPolicy
来处理具体的证书验证逻辑。
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
关于请求服务器数据这块值得讲一下,在NSURLConnection
接收服务器数据时,AFN
经过建立了一个outputStream
,来承载和组织具体的数据,而且在内存中进行存储。当没有可用空间或发生其余错误时,会经过streamError
的方式进行体现。
当网络请求结束时,会调用didFinishLoading
方法,AFN
会从outputStream
中拿出数据并赋值给responseData
,当作返回值数据使用。
- (void)connection:(NSURLConnection __unused *)connection didReceiveData:(NSData *)data { NSUInteger length = [data length]; while (YES) { NSInteger totalNumberOfBytesWritten = 0; if ([self.outputStream hasSpaceAvailable]) { const uint8_t *dataBuffer = (uint8_t *)[data bytes]; NSInteger numberOfBytesWritten = 0; while (totalNumberOfBytesWritten < (NSInteger)length) { numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; if (numberOfBytesWritten == -1) { break; } totalNumberOfBytesWritten += numberOfBytesWritten; } break; } else { [self.connection cancel]; if (self.outputStream.streamError) { [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError]; } return; } } if (self.downloadProgress) { self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength); } } - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; [self.outputStream close]; if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; }
有outputStream
,也会有与之对应的inputStream
,inputStream
实现很简单,就是修改NSMutableURLRequest
的HTTPBodyStream
。
- (NSInputStream *)inputStream { return self.request.HTTPBodyStream; } - (void)setInputStream:(NSInputStream *)inputStream { NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; mutableRequest.HTTPBodyStream = inputStream; self.request = mutableRequest; }
在建立AFHTTPRequestOperation
时会将success
和failure
的block
传给operation
,而且在operation
执行完成并回调completionBlock
时,执行这两个block
代码。可是因为completionBlock
中直接使用了self
,致使了循环引用的问题。
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id __nullable responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { self.completionBlock = ^{ // something code ... }; }
completionBlock
的循环引用是AFN
有意而为之的,为的就是保持operation
的生命周期,以保证请求处理完成并接收返回的block
回调。
对于循环引用的生命周期,AFN
采起的是主动打破循环引用的方式,也就是重写父类的completionBlock
,而且在调用block
结束后,主动将completionBlock
赋值为nil
,从而主动打破循环引用。
- (void)setCompletionBlock:(void (^)(void))block { if (!block) { [super setCompletionBlock:nil]; } else { __weak __typeof(self)weakSelf = self; [super setCompletionBlock:^ { __strong __typeof(weakSelf)strongSelf = weakSelf; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group(); dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue(); #pragma clang diagnostic pop dispatch_group_async(group, queue, ^{ block(); }); dispatch_group_notify(group, url_request_operation_completion_queue(), ^{ [strongSelf setCompletionBlock:nil]; }); }]; } }
AFNetworking
中还有很重要的一部分,就是Reachability
,用来作网络状态监控的。AFNetworking
、YYKit
、苹果官方都提供有Reachability
的API
使用,内部实现原理基本差很少。
代码实现也很简单,主要依赖SystemConfiguration.framework
框架的SCNetworkReachability
,注册一个Callback
而后等着回调就能够。这里讲一下核心逻辑,一些细枝末节的就忽略了。
Reachability
提供了两种初始化方法,一种是经过域名初始化的managerForDomain:
方法,传入一个域名,基于这个域名的访问状况来判断当前网络状态。另外一种是经过地址初始化的managerForAddress:
方法,建立一个sockaddr_in
对象,并基于这个对象来判断网络状态。
+ (instancetype)managerForAddress:(const void *)address { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; CFRelease(reachability); return manager; } + (instancetype)manager { struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_len = sizeof(address); address.sin_family = AF_INET; return [self managerForAddress:&address]; }
下面startMonitoring
中是开启网络监测的核心代码,主要逻辑是设置了两个Callback
,一个是block
的一个是函数的,并添加到Runloop
中开始监控。由此能够推测,Reachability
的代码实现主要依赖Runloop
的事件循环,而且在事件循环中判断网络状态。
当网络发生改变时,就会回调AFNetworkReachabilityCallback
函数,回调有三个参数。target
是SCNetworkReachabilityRef
对象,flags
是网络状态,info
是咱们设置的block
回调参数。回调Callback
函数后,内部会经过block
以及通知的形式,对外发出回调。
- (void)startMonitoring { [self stopMonitoring]; if (!self.networkReachability) { return; } __weak __typeof(self)weakSelf = self; AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; 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_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); } }); } - (void)stopMonitoring { if (!self.networkReachability) { return; } SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); } static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info); }
AFNetworking
对请求数据的序列化,以及返回数据的反序列化作了不少处理。使开发者只须要传入一个字典便可构建请求参数,无需处理拼接到URL
后面或参数转换为body
二进制的细节,这些都由AFNetworking
内部处理并进行容错,开发者只须要指定请求方式便可。
经过AFNetworking
实现https
也很方便,AFSecurityPolicy
能够很好的管理CA
以及自签名证书,以及处理https
请求过程当中的一些逻辑,咱们只须要告诉AFNetworking
怎么处理便可,若是不指定处理方式则使用默认CA
证书的方式处理。
AFNetworking
对于后台下载以及断点续传有很好的支持,咱们能够在AFNetworking
的基础上,很简单的就完成一个下载模块的设计。若是本身写后台下载和断点续传的代码,工做量仍是不小的。
而且AFNetworking
在网络库的设计上还提供了很强的自定义性,例如指定证书、URL
缓存处理,以及下载过程当中不一样下载阶段的处理。若是没有提供自定义处理,则使用默认处理方式。