AFNetworking 简介
一、AFNetworking 版本
AFNetworking 是 iOS 最常用的网络框架,虽然系统也有 NSURLSession ,但是一般不会直接用它。AFNetworking经过了三个大版本,现在用的大多数都是3.x的版本。
AFNetworking 经历以下三个阶段的发展:
1.0版本 : 基于NSURLConnection的封装。2.0版本 : 两套实现,分别基于NSURLConnection和NSURLSession,是转向NSURLSession的过渡版。3.0版本 : 基于NSURLSession的封装。
二、AFNetworking 构成
文件组成
将AFNetworking.h放到了Supporting Files里面:
除去Support Files,可以看到AF分为如下五个功能模块:
网络通信模块(核心)(AFURLSessionManager、AFHTTPSessionManager):负责处理网络请求的两个Manager,主要实现都在AFURLSessionManager中。网络状态监听模块(Reachability):监控网络状态。网络通信安全策略模块(Security):处理网络安全和HTTPS相关的。网络通信信息序列化/反序列化模块(Serialization):请求和返回数据的格式化器。对于iOS UIKit库的拓展(UIKit)。
AFNetworking 模块解析
一、AFURLSessionManager
在AFN3.0中,网络请求的manager主要有AFHTTPSessionManager和AFURLSessionManager构成,二者为父子关系。这两个类职责划分清晰,父类负责处理一些基础的网络请求代码,并且接受NSURLRequest对象,而子类则负责处理和http协议有关的逻辑。网络通信模块 AFURLSessionManager 与 AFHTTPSessionManager 负责处理网络请求的两个Manager,AFHTTPSessionManager 是继承 AFURLSessionManager 的,相当于对AFURLSessionManager 的再次封装。AFN的这套设计很便于扩展,如果以后想增加FTP协议的处理,则基于AFURLSessionManager创建子类即可。子类中只需要进行很少的代码处理,创建一个NSURLRequest对象后调用父类代码,由父类去完成具体的请求操作。AFURLSessionManager类,所包含的属性:
属性类型含义
sessionNSURLSession会话管理器管理的会话operationQueueNSOperationQueue用于执行代理回调方法的队列responseSerializerid返回数据的解析器,默认是AFJSONResponseSerializer解析器securityPolicyAFSecurityPolicy建立安全会话时,使用的安全策略reachabilityManagerAFNetworkReachabilityManager网络状态管理器tasksNSArray<NSURLSessionTask *>当前会话所关联的所有任务,包含数据请求、下载、上传任务dataTasksNSArray<NSURLSessionDataTask *>当前会话所关联的数据请求任务uploadTasksNSArray<NSURLSessionUploadTask *>当前会话所关联的上传任务downloadTasksNSArray<NSURLSessionDownloadTask *>当前会话所关联的下载任务completionQueuedispatch_queue_t指定completionBlock 执行时的队列,默认为 NULL,使用主队列completionGroupdispatch_group_t指定completionBlock相关联的组,默认为NULL,将创建一个私有的组attemptsToRecreatUploadTasksForBackgroundSessionBOOL指明当上传后台上传任务失败时,是否重新尝试创建,默认值为NO
① 创建sessionManager
AFHTTPSessionManager 类的初始化方法中并没有太多实现代码,其内部调用的都是父类 AFURLSessionManager 的 initWithSessionConfiguration 方法,下面是此方法内部的一些关键代码。
在初始化方法中包含一个参数sessionConfiguration,如果没有传入的话默认是使用系统的defaultConfiguration,我们创建是一般都不会自定义configuration,所以大多数都是系统的。
if (!configuration
) {
configuration
= [NSURLSessionConfiguration defaultSessionConfiguration
];
}
随后是NSURLSession的初始化代码,NSURLSession的初始化方式有两种,一种是使用系统的共享session,另一种是自己创建session。AFN选择的是创建自己的session,并且每个请求都会创建一个独立的session。可以通过NSURLSession进行连接复用,这样可以避免很多握手和挥手的过程,提高网络请求速度,苹果允许iOS设备上一个域名可以有四个连接同时存在。但是由于AFN的实现是每个请求都创建一个session,所以就不能进行连接复用。所以可以通过在外面对AFN进行二次封装,将AFHTTPSessionManager复用为单例对象,通过复用sessionManager的方式,来进行连接的复用。但是这种方案对于不同的requestSerializer、responseSerializer等情况,还是要做特殊兼容,所以最好建立一个sessionManager池,对于同类型的sessionManager直接拿出来复用,否则就创建新的。
[NSURLSession sharedSession
];
[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中可以发现:在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
];
}
}];
② 创建task
在AFURLSessionManager中进行task的创建,task的类型总共分为三种,dataTask、uploadTask、downloadTask,AFN并没有对streamTask进行处理。AFHTTPSessionManager在创建GET、POST等请求时,本质上都是调用了下面的方法或其类似的方法,方法内部会创建一个task对象,并调用 addDelegateForDataTask 将后面的处理交给 AFURLSessionManagerTaskDelegate 来完成。随后会将task返回给调用方,调用方获取到task对象后,也就是子类AFHTTPSessionManager,会调用resume方法开始请求。除了普通请求外,upload、download都有类似的处理。
- (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
;
}
在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得到回调结果。
③ AFURLSessionManagerTaskDelegate
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];
}
④ AFURLSessionTaskSwizzling
看过源码的话,可以发现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
));
}
}
⑤ clang预编译指令
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
];
});
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
;
}
⑦ NSOperationQueue
AFN在创建AFURLSessionManager的operationQueue时,将其最大并发数设置为1。这是因为在创建NSURLSSession时,苹果要求网络请求回来的数据顺序执行,为了保证代理方法的执行顺序,所以需要串行的调用NSURLSSession的代理方法。
二、AFHTTPSessionManager
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
AFURLRequestSerialization负责创建NSMutableURLRequest请求对象,并对request进行请求的参数拼接、设置缓存策略、设置请求头等关于请求相关的配置。 AFURLRequestSerialization 并不是一个类,而是一个文件,其中包含三个requestSerializer请求对象,分别对应着不同的请求序列化器。
AFHTTPRequestSerializer:普通请求。AFJSONRequestSerializer:JSON请求。AFPropertyListRequestSerializer:一种特殊的xml格式请求。
① AFURLRequestSerialization协议
在文件中定义了同名的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对象,中间的过程并不复杂,主要是设置请求头和拼接参数,逻辑很清晰。
② AFQueryStringPair
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
;
}
③ 设置NSMutableURLRequest
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
) {
} success
:^(NSURLSessionDataTask
* _Nonnull task
, id _Nullable responseObject
) {
} failure
:nil
];
进行表单提交时,可以直接传入文件,也可以传入路径。表单提交可以同时提交多个文件,理论上数量不受限制。
⑤ 缓存策略
AFN的缓存策略和NSURLCache的缓存策略一致,并且直接使用系统的枚举,这对iOS开发者是非常友好的。下面是枚举定义,忽略掉一些unimplemented的,和一些重定向到已有枚举的。
NSURLRequestUseProtocolCachePolicy,使用协议指定的缓存策略。NSURLRequestReloadIgnoringLocalCacheData,忽略缓存,直接发起请求。NSURLRequestReturnCacheDataElseLoad,不验证缓存过期时间,如果有则使用缓存数据,如果不存在则请求服务器。NSURLRequestReturnCacheDataDontLoad,不验证缓存过期时间,如果有则使用缓存数据,如果不存在则请求失败。NSURLRequestReloadIgnoringLocalAndRemoteCacheData,忽略本地缓存,以及代理等中间介质的缓存。NSURLRequestReloadRevalidatingCacheData,和数据的源服务器验证数据合法性,如果可以用就直接使用缓存数据,否则从服务器请求数据。
typedef NS_ENUM(NSUInteger
, NSURLRequestCachePolicy
) {
NSURLRequestUseProtocolCachePolicy
= 0,
NSURLRequestReloadIgnoringLocalCacheData
= 1,
NSURLRequestReturnCacheDataElseLoad
= 2,
NSURLRequestReturnCacheDataDontLoad
= 3,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData
= 4,
NSURLRequestReloadRevalidatingCacheData
= 5,
};
四、AFURLResponseSerialization
AFURLResponseSerialization负责处理response相关的逻辑,其功能主要是设置acceptType、编码格式和处理服务器返回数据。同样的,AFURLResponseSerialization也有同名的协议,每个子类都遵循代理方法并实现不同的返回值处理代码。
- (nullable id
)responseObjectForResponse
:(nullable NSURLResponse
*)response
data
:(nullable NSData
*)data
error
:(NSError
* _Nullable __autoreleasing
*)error
;
和AFURLRequestSerialization一样,AFURLResponseSerialization由一个父类和六个子类构成,子类中有一个是Mac的,所以这里不做分析,子类的职责只是对acceptType做修改以及处理具体的返回数据。
AFHTTPResponseSerializer:公共父类,处理返回值类型为NSData二进制。AFJSONResponseSerializer:JSON返回数据,也是默认类型。AFXMLParserResponseSerializer,处理XML返回数据,由系统NSXMLParser负责处理。AFPropertyListResponseSerializer:处理特殊XML返回数据,也就是plist数据。AFImageResponseSerializer:处理图片返回数据,这个类型用的也比较多。AFCompoundResponseSerializer:处理复杂数据,返回结果类型有多种。
容错处理
由于服务器有时候会返回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
;
}
五、AFNetworkReachabilityManager
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
);
}
六、AFSecurityPolicy
① 验证处理
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类型的枚举,表示证书验证的情况,此枚举包含下面几个具体值:
NSURLSessionAuthChallengeUseCredential 使用当前证书建立SSL连接,并处理后续请求NSURLSessionAuthChallengePerformDefaultHandling 使用默认的处理方式,当前证书被忽略NSURLSessionAuthChallengeCancelAuthenticationChallenge 验证不通过,取消整个网络请求NSURLSessionAuthChallengeRejectProtectionSpace 这次验证被忽略,但不取消网络请求
② Security
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>
③ SSL Pinning
AFSecurityPolicy进行SSL Pinning验证的方式分为以下三种,如果是None则会执行正常CA验证的流程,其他两种都是自签名的流程。AFN中默认的调用是defaultPolicy方法,其内部设置的是AFSSLPinningModeNone模式。
AFSSLPinningModeNone 正常流程,通过CA机构颁发的公钥,对服务器下发的证书验证数字签名,并且获得公钥。AFSSLPinningModeCertificate 不通过CA的流程进行验证,而是通过本地内置的服务端证书进行验证,验证过程分为两步。首先验证证书是否过期或失效,其次验证本地是否包含此证书。AFSSLPinningModePublicKey 不进行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
;
AFNetworking 整体流程
使用AFNetworking来实现一个GET网络请求:
GET请求实现如下:
AFHTTPSessionManager
*manager
= [AFHTTPSessionManager manager
];
NSMutableURLRequest
* request
= [NSMutableURLRequest requestWithURL
:[NSURL URLWithString
:requestUrl
]];
[manager GET
:@"htts://www.baidu.com" parameters
:nil progress
:^(NSProgress
* _Nonnull downloadProgress
) {
} success
:^(NSURLSessionDataTask
* _Nonnull task
, id _Nullable responseObject
) {
} failure
:^(NSURLSessionDataTask
* _Nullable task
, NSError
* _Nonnull error
) {
}];
使用AFNetworking进行GET网络请求比较简单,GET方法点进去,可以看到:创建了一个dataTask,然后启动了这个task;
- (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
;
}
dataTask是如何创建的?分为两步:
第一步是创建了一个 NSMutableRequest 对象;第二步是使用这个 NSMutableRequest 对象创建了 NSURLSessionDataTask 对象,到这里是不是就有点像用 NSURLSession 做网络请求呢?
- (NSURLSessionDataTask
*)dataTaskWithHTTPMethod
:(NSString
*)method
URLString
:(NSString
*)URLString
parameters
:(id
)parameters
uploadProgress
:(nullable
void (^)(NSProgress
*uploadProgress
)) uploadProgress
downloadProgress
:(nullable
void (^)(NSProgress
*downloadProgress
)) downloadProgress
success
:(void (^)(NSURLSessionDataTask
*, id
))success
failure
:(void (^)(NSURLSessionDataTask
*, NSError
*))failure
{
NSError
*serializationError
= nil
;
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
:^(NSURLResponse
* __unused response
, id responseObject
, NSError
*error
) {
if (error
) {
if (failure
) {
failure(dataTask
, error
);
}
} else {
if (success
) {
success(dataTask
, responseObject
);
}
}
}];
return dataTask
;
}
① 创建 NSMutableRequest 对象
NSMutableRequest 的 requestWithMethod 方法:
- (NSMutableURLRequest
*)requestWithMethod
:(NSString
*)method
URLString
:(NSString
*)URLString
parameters
:(id
)parameters
error
:(NSError
*__autoreleasing
*)error
{
NSParameterAssert(method
);
NSParameterAssert(URLString
);
NSURL
*url
= [NSURL URLWithString
:URLString
];
NSParameterAssert(url
);
NSMutableURLRequest
*mutableRequest
= [[NSMutableURLRequest alloc
] initWithURL
:url
];
mutableRequest
.HTTPMethod
= method
;
for (NSString
*keyPath
in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject
:keyPath
]) {
[mutableRequest setValue
:[self valueForKeyPath
:keyPath
] forKey
:keyPath
];
}
}
mutableRequest
= [[self requestBySerializingRequest
:mutableRequest withParameters
:parameters error
:error
] mutableCopy
];
return mutableRequest
;
}
从上面的源码中可以看到,用KVC给mutableRequest的属性设值的部分;
for (NSString
*keyPath
in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject
:keyPath
]) {
[mutableRequest setValue
:[self valueForKeyPath
:keyPath
] forKey
:keyPath
];
}
}
然后可以看到AFHTTPRequestSerializerObservedKeyPaths()这个方法,它做了什么呢?其实,就是把 NSMutableURLRequest 的一些属性转化成字符串数组返回。然后如果self.mutableObservedChangedKeyPaths包括这个数组中的某个属性,那么就给mutableRequest对象的这个属性通过KVC设值。 那么,这个mutableObservedChangedKeyPaths到底又是什么呢?
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
;
}
我们可以在使用AFNetworking进行网络请求的时候,在AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];这里加上以下代码:
manager
.requestSerializer
.allowsCellularAccess
= YES
;
然后在 - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError *__autoreleasing *)error 这个方法里打断点,打印 self.mutableObservedChangedKeyPaths 的值,打印出来的结果如下:
(lldb
) po
self.mutableObservedChangedKeyPaths
{(
allowsCellularAccess
)}
(lldb
)
这就是在外面通过 requestSerializer 设置的一些网络请求的属性,在内部转化到了 self.mutableObservedChangedKeyPaths 这个数组内部,再通过比对给request的这些属性设值。紧接着看一下在外部给 requestSerializer 的属性设值是怎么转化到 self.mutableObservedChangedKeyPaths 这个数组内部?
- (void)setAllowsCellularAccess
:(BOOL
)allowsCellularAccess
{
[self willChangeValueForKey
:NSStringFromSelector(@selector(allowsCellularAccess
))];
_allowsCellularAccess
= allowsCellularAccess
;
[self didChangeValueForKey
:NSStringFromSelector(@selector(allowsCellularAccess
))];
}
当设置 manager.requestSerializer.allowsCellularAccess = YES 时代码会执行其set方法,在set方法里面,手动触发了KVO,再接着,在KVO的回调里,通过如下逻辑,就把这个key加到了self.mutableObservedChangedKeyPaths数组里。
- (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
];
}
}
}
至此,一个NSMutableRequest 对象封装了请求的起始行、请求头,请求体,除此之外还设置了请求的一些属性,比如allowsCellularAccess等,到这里第一步也就完成了起始行的配置,以及请求的一些属性的设置,那么第二步就是为了完成请求头和请求体的配置等工作:
mutableRequest
= [[self requestBySerializingRequest
:
mutableRequest withParameters
:parameters error
:error
] mutableCopy
];
② 用 NSMutableRequest 对象创建 NSURLSessionDataTask 对象
- (NSURLRequest
*)requestBySerializingRequest
:(NSURLRequest
*)request
withParameters
:(id
)parameters
error
:(NSError
*__autoreleasing
*)error
{
NSParameterAssert(request
);
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
;
}
为 NSMutableURLRequest 设置请求头: self.HTTPRequestHeaders 是在 AFHTTPRequestSerializer 的初始化方法里面创建的,self.HTTPRequestHeaders 主要包含两个请求头,即 Accept-Language 和 User-Agent ,这里就是为mutableRequest设置了这两个请求头。
NSMutableURLRequest
*mutableRequest
= [request mutableCopy
];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock
:^(id field
, id value
, BOOL
* __unused stop
) {
if (![request valueForHTTPHeaderField
:field
]) {
[mutableRequest setValue
:value forHTTPHeaderField
:field
];
}
}];
将参数进行转化,并用=,&连起来:这一步的核心就是query = AFQueryStringFromParameters(parameters);这一行,我们看一下AFQueryStringFromParameters()方法的实现:
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;
}
}
}
通过AFQueryStringPairsFromDictionary()这个方法把parameters转化为一个个AFQueryStringPair对象,然后把这些对象都加入mutablePairs这个数组中,最后使用&把数组中的对象连起来,组成一个字符串。
NSString
* AFQueryStringFromParameters(NSDictionary
*parameters
) {
NSMutableArray
*mutablePairs
= [NSMutableArray array
];
for (AFQueryStringPair
*pair
in AFQueryStringPairsFromDictionary(parameters
)) {
[mutablePairs addObject
:[pair URLEncodedStringValue
]];
}
return [mutablePairs componentsJoinedByString
:@"&"];
}
具体 AFQueryStringPair 这个类,这个类有两个属性,一个是field,一个是value,还有一个方法- (NSString *)URLEncodedStringValue,这个方法就是把field和value这两个属性用=连起来,组成字符串。
- (instancetype
)initWithField
:(id
)field value
:(id
)value
{
self = [super init
];
if (!self) {
return nil
;
}
self.field
= field
;
self.value
= value
;
return self;
}
- (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
])];
}
}
接下来分析 AFQueryStringPairsFromDictionary() 这个方法处理 parameters 的策略;
id parameters
= @{@"name" : @"乔布斯",
@"sex" : @"boy"};
name
=乔布斯
&sex
=boy
id parameters
= @{@"name" : @"乔布斯",
@"sex" : @"boy",
@"friends" : @{@"name" : @"雷布斯", @"sex" : @"boy"}
};
name
=乔布斯
&sex
=boy
&friends
[name
]=雷布斯
&friends
[sex
]=boy
id parameters2
= @[@"比尔盖茨", @"boy"];
[]=比尔盖茨
&[]=boy
第二步至此结束,再返回去,如果方法是GET、HEAD、DELETE,则直接将第二步得到的字符串粘贴在请求URL后面。当方法不是上面的方法时,需要为mutableRequest设置Content-Type这个请求头,并且将上面第二步得到的参数字符串作为mutableRequest的请求体。注意:为什么对于GET、HEAD、DELETE这三个方法不需要设置Content-Type这个请求头呢?这是因为根据RFC 7231的规定,只有PUT和POST请求的时候,Content-Type才是必须的。到这里request的创建和设置就完成了,下面继续看dataTask的创建。
- (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
;
}
url_session_manager_create_task_safely 创建了一个同步串行队列:
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();
}
}
然后是 addDelegateForDataTask 方法的实现:
[self addDelegateForDataTask
:dataTask
uploadProgress
:uploadProgressBlock
downloadProgress
:downloadProgressBlock
completionHandler
:completionHandler
];
- (void)addDelegateForDataTask
:(NSURLSessionDataTask
*)dataTask
uploadProgress
:(nullable
void (^)(NSProgress
*uploadProgress
)) uploadProgressBlock
downloadProgress
:(nullable
void (^)(NSProgress
*downloadProgress
)) downloadProgressBlock
completionHandler
:(void (^)(NSURLResponse
*response
, id responseObject
, NSError
*error
))completionHandler
{
AFURLSessionManagerTaskDelegate
*delegate
= [[AFURLSessionManagerTaskDelegate alloc
] init
];
delegate
.manager
= self;
delegate
.completionHandler
= completionHandler
;
dataTask
.taskDescription
= self.taskDescriptionForSessionTasks
;
[self setDelegate
:delegate forTask
:dataTask
];
delegate
.uploadProgressBlock
= uploadProgressBlock
;
delegate
.downloadProgressBlock
= downloadProgressBlock
;
}
梳理一下AFURLSessionManager,AFURLSessionManagerTaskDelegate,NSURLSessionDataTask这三个类之间的关系: 在AFURLSessionManager这个类中,创建了session对象,并指定自己为session的delegate,这意味着AFURLSessionManager这个类需要实现NSURLSessionDelegate, NSURLSessionTaskDelegate ,NSURLSessionDataDelegate, NSURLSessionDownloadDelegate这些协议的方法。实现逻辑如下:
- (instancetype
)initWithSessionConfiguration
:(NSURLSessionConfiguration
*)configuration
{
self = [super init
];
if (!self) {
return nil
;
}
if (!configuration
) {
configuration
= [NSURLSessionConfiguration defaultSessionConfiguration
];
}
self.sessionConfiguration
= configuration
;
self.operationQueue
= [[NSOperationQueue alloc
] init
];
self.operationQueue
.maxConcurrentOperationCount
= 1;
self.session
= [NSURLSession sessionWithConfiguration
:self.sessionConfiguration delegate
:self delegateQueue
:self.operationQueue
];
self.responseSerializer
= [AFJSONResponseSerializer serializer
];
self.securityPolicy
= [AFSecurityPolicy defaultPolicy
];
#if !TARGET_OS_WATCH
self.reachabilityManager
= [AFNetworkReachabilityManager sharedManager
];
#endif
self.mutableTaskDelegatesKeyedByTaskIdentifier
= [[NSMutableDictionary alloc
] init
];
self.lock
= [[NSLock alloc
] init
];
self.lock
.name
= AFURLSessionManagerLockName
;
[self.session getTasksWithCompletionHandler
:^(NSArray
*dataTasks
, NSArray
*uploadTasks
, NSArray
*downloadTasks
) {
for (NSURLSessionDataTask
*task
in dataTasks
) {
[self addDelegateForDataTask
:task uploadProgress
:nil downloadProgress
:nil completionHandler
:nil
];
}
for (NSURLSessionUploadTask
*uploadTask
in uploadTasks
) {
[self addDelegateForUploadTask
:uploadTask progress
:nil completionHandler
:nil
];
}
for (NSURLSessionDownloadTask
*downloadTask
in downloadTasks
) {
[self addDelegateForDownloadTask
:downloadTask progress
:nil destination
:nil completionHandler
:nil
];
}
}];
return self;
}
可以看到这些代理方法已经被实现:
但是有一些代理方法其实是在代理方法内部交给了AFURLSessionManagerTaskDelegate这个类去实现,比如下面的代理方法:
- (void)URLSession
:(NSURLSession
*)session
dataTask
:(NSURLSessionDataTask
*)dataTask
didReceiveData
:(NSData
*)data
{
AFURLSessionManagerTaskDelegate
*delegate
= [self delegateForTask
:dataTask
];
[delegate URLSession
:session dataTask
:dataTask didReceiveData
:data
];
if (self.dataTaskDidReceiveData
) {
self.dataTaskDidReceiveData(session
, dataTask
, data
);
}
}
AFURLSessionManager 需要将 NSURLSessionDataTask 和 AFURLSessionManagerTaskDelegate 对应起来,这样在 NSURLSessionDataTask 的代理方法中就可以获取到task对应的 AFURLSessionManagerTaskDelegate 然后交给它去处理。
接着:再回到setDelegate 和 addDelegateForDataTask:
- (void)setDelegate
:(AFURLSessionManagerTaskDelegate
*)delegate
forTask
:(NSURLSessionTask
*)task
{
NSParameterAssert(task
);
NSParameterAssert(delegate
);
[self.lock lock
];
self.mutableTaskDelegatesKeyedByTaskIdentifier
[@(task
.taskIdentifier
)] = delegate
;
[delegate setupProgressForTask
:task
];
[self addNotificationObserverForTask
:task
];
[self.lock unlock
];
}
- (void)addDelegateForDataTask
:(NSURLSessionDataTask
*)dataTask
uploadProgress
:(nullable
void (^)(NSProgress
*uploadProgress
)) uploadProgressBlock
downloadProgress
:(nullable
void (^)(NSProgress
*downloadProgress
)) downloadProgressBlock
completionHandler
:(void (^)(NSURLResponse
*response
, id responseObject
, NSError
*error
))completionHandler
{
AFURLSessionManagerTaskDelegate
*delegate
= [[AFURLSessionManagerTaskDelegate alloc
] init
];
delegate
.manager
= self;
delegate
.completionHandler
= completionHandler
;
dataTask
.taskDescription
= self.taskDescriptionForSessionTasks
;
[self setDelegate
:delegate forTask
:dataTask
];
delegate
.uploadProgressBlock
= uploadProgressBlock
;
delegate
.downloadProgressBlock
= downloadProgressBlock
;
}
[delegate setupProgressForTask:task];即为task的一些下载和上传的属性使用KVO添加观察者:
[task addObserver
:self forKeyPath
:@"state" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesSentContext
];
[task addObserver
:self forKeyPath
:@"countOfBytesSent" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesSentContext
];
[task addObserver
:self forKeyPath
:@"state" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesReceivedContext
];
[task addObserver
:self forKeyPath
:@"countOfBytesReceived" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesReceivedContext
];
然后在检测到属性改变的回调里,改变自己的属性值:
- (void)observeValueForKeyPath
:(NSString
*)keyPath ofObject
:(id
)object change
:(NSDictionary
<NSString
*,id
> *)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 ([keyPath isEqualToString
:NSStringFromSelector(@selector(countOfBytesExpectedToReceive
))]) {
self.downloadProgress
.totalUnitCount
= [change
[NSKeyValueChangeNewKey
] longLongValue
];
} else if ([keyPath isEqualToString
:NSStringFromSelector(@selector(countOfBytesSent
))]) {
self.uploadProgress
.completedUnitCount
= [change
[NSKeyValueChangeNewKey
] longLongValue
];
} else if ([keyPath isEqualToString
:NSStringFromSelector(@selector(countOfBytesExpectedToSend
))]) {
self.uploadProgress
.totalUnitCount
= [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
);
}
}
}
AFURLSessionManagerTaskDelegate 的作用:
分担一部分AFURLSessionManager的工作,在一些AFURLSessionManager实现的代理方法中,直接调用了AFURLSessionManagerTaskDelegate的相关方法。对于上传和下载,监听上传和下载的进度并产生回调,具体过程如下:
delegate
.uploadProgressBlock
= uploadProgressBlock
;
delegate
.downloadProgressBlock
= downloadProgressBlock
;
[task addObserver
:self forKeyPath
:@"state" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesSentContext
];
[task addObserver
:self forKeyPath
:@"countOfBytesSent" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesSentContext
];
[task addObserver
:self forKeyPath
:@"state" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesReceivedContext
];
[task addObserver
:self forKeyPath
:@"countOfBytesReceived" options
:(NSKeyValueObservingOptions
)0 context
:AFTaskCountOfBytesReceivedContext
];
当task的属性发生变化时,会执行这里:self.downloadProgress和self.uploadProgress的属性也会发生变化,又由于上文中监听了self.downloadProgress和self.uploadProgress的fractionCompleted的属性改变,因此又会回调这里,执行downloadProgressBlock和uploadProgressBlock。
- (void)observeValueForKeyPath
:(NSString
*)keyPath
ofObject
:(id
)object
change
:(__unused NSDictionary
*)change
context
:(void *)context
{
if (context
== AFTaskCountOfBytesSentContext
|| context
== AFTaskCountOfBytesReceivedContext
) {
if ([keyPath isEqualToString
:NSStringFromSelector(@selector(countOfBytesSent
))]) {
if ([object countOfBytesExpectedToSend
] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress
:[object countOfBytesSent
] / ([object countOfBytesExpectedToSend
] * 1.0f) animated
:self.af_uploadProgressAnimated
];
});
}
}
if ([keyPath isEqualToString
:NSStringFromSelector(@selector(countOfBytesReceived
))]) {
if ([object countOfBytesExpectedToReceive
] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress
:[object countOfBytesReceived
] / ([object countOfBytesExpectedToReceive
] * 1.0f) animated
:self.af_downloadProgressAnimated
];
});
}
}
if ([keyPath isEqualToString
:NSStringFromSelector(@selector(state
))]) {
if ([(NSURLSessionTask
*)object state
] == NSURLSessionTaskStateCompleted
) {
@try {
[object removeObserver
:self forKeyPath
:NSStringFromSelector(@selector(state
))];
if (context
== AFTaskCountOfBytesSentContext
) {
[object removeObserver
:self forKeyPath
:NSStringFromSelector(@selector(countOfBytesSent
))];
}
if (context
== AFTaskCountOfBytesReceivedContext
) {
[object removeObserver
:self forKeyPath
:NSStringFromSelector(@selector(countOfBytesReceived
))];
}
}
@catch (NSException
* __unused exception
) {}
}
}
}
}
AFNetworking 设计技巧
一、bundleForClass
在使用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的方式加锁,防止从各个线程调用过来的通知造成资源抢夺的问题。
AFNetworking 网络请求过程总结
首先创建一个mutableRequest,设置其请求方法,然后根据用户的设置,设置mutableRequest的allowsCellularAccess,timeoutInterval等属性;然后设置mutableRequest的公共请求头,主要是两个,User-Agent和Accept-Language;再把请求的parameters转化为键值对,用=,&连起来组成字符串,如果请求方法是GET,HEAD, DELETE这三个方法之一,则直接把parameters字符串粘贴在请求URL后面,否则就要把parameters字符串设置成请求的请求体,并设置Content-Type请求头;到这里request相关都配置好了,并且这些配置都是在requestSerializer相关类中完成的。创建dataTask,首先创建一个session对象,根据session对象和之前创建的request对象来创建一个dataTask,然后创建一个AFURLSessionManagerTaskDelegate的对象,把这个对象和dataTask关联起来,让AFURLSessionManagerTaskDelegate对象通过KVO监听dataTask的上传下载进度,并执行相应的回调。通过resume启动dataTask。在请求完成的代理方法中,会使用responseSerializer来处理response,首先检查response的有效性,然后将其转化为相应的类型。