本文是我对AFNetworking
代码模块的总结,想做到有问题知道找谁(找AFNetworking
的哪个模块),而非完完整整的源码解析,要看源码解析我推荐辉哥解析AFNetworking源码的系列文章.本文大体基于AFNetworking3.0
进行学习.后面也会有对比AFNetworking3.0
与AFNetworking2.0
的内容.
1.概观
AFNetworking
大概可以分为:
主干:
请求组织模块
管理者
返回数据解析模块
补充:
网络通畅检测模块
安全模块
UI关联模块
请求组织模块
,管理者
,返回数据解析模块
分为主干,是因为这三者连起来说可以清晰的描绘出AFNetworking
网络请求的具体流程.
这样一分方便自己写总结,不是强调谁比较重要,都重要,😁 😁 😁 .
2.主干
- (void)request_delegate{
_allData = [NSMutableData data];
NSURLSessionConfiguration *configura = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configura delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:@"http://c.3g.163.com/photo/api/list/0096/4GJ60096.json"];
NSURLRequest * request = [[NSURLRequest alloc]initWithURL:url];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
NSLog(@"%@",[error localizedDescription]);
return;
}
NSError *er = nil;
id result = [NSJSONSerialization JSONObjectWithData:_allData options:NSJSONReadingMutableContainers error:&er];
NSLog(@"result: %@", result);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
[_allData appendData:data];
}
- (void)afn_request{
AFHTTPSessionManager * manger = [AFHTTPSessionManager manager];
manger.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html",@"text/plain", nil];
[manger GET:@"http://c.3g.163.com/photo/api/list/0096/4GJ60096.json" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary * responseData) {
NSLog(@"responseData-----%@",responseData);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
}
以上是一份用NSURLSession
请求数据的代码和一份用AFNetworking
请求数据的代码.AFNetworking
的代码很简洁是因为很多操作让封装好的工具类
给做了.
2.1 请求组织模块
我们的请求组织
NSURL *url = [NSURL URLWithString:@"http://c.3g.163.com/photo/api/list/0096/4GJ60096.json"];
NSURLRequest * request = [[NSURLRequest alloc]initWithURL:url];
我们的请求组织就上面两句,那是因为我们做的请求本身比较简单.NSURLRequest
内部其实有很多参数.
NSURLRequest包含请求信息有:
请求方法:GET,POST..
URL
参数
超时时间
HTTP头信息:USER-PARAM...
等等
AFNetworking
负责请求参数组织的是AFHTTPRequestSerializer
.
AFHTTPRequestSerializer
的工作就是收集我们通过简单方法传入的参数,生成一个NSURLRequest
.
- GET:parameters:progress:success:failure:
- dataTaskWithHTTPMethod:URLString:parameters:uploadProgressdownloadProgress:success:failure:
- requestWithMethod:URLString:parameters:error:
- requestBySerializingRequest:withParameters:error:
- (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;
//zc read:改mutableRequest的部分属性
//[allowsCellularAccess,cachePolicy,HTTPShouldHandleCookies,HTTPShouldUsePipelining,networkServiceType,timeoutInterval];
//外围改变AFHTTPRequestSerializer的任何属性都会被记录下来,而当以上五个属性被修改,则需要将属性同步到生成的mutableRequest上
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;
}
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//zc read:改mutableRequest.HTTPRequestHeaders内键值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
//zc read:parameters由字典(@{@"kk1":@"vv1",@"kk2":@"vv2"})转成字符串(kk1=vv1&kk2=vv2)
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;
}
}
}
//zc read:self.HTTPMethodsEncodingParametersInURI==>{GET,HEAD,DELETE},只有这三个方法会将参数拼接到url后面
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 {
// #2864: an empty string is a valid x-www-form-urlencoded payload
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;
}
我们通过- GET:parameters:progress:success:failure:
传入与生成NSURLRequest
有关的只有:
URL
请求方法
parameters
而AFHTTPRequestSerializer
会在组织生成NSURLRequest
的时候加入许多其他的参数,还会帮我们调整URL(如:GET请求的parameters,会帮我们拼在原来得URL后面).
当然简单的网络请求我们并不会直接用到AFHTTPRequestSerializer
.我们如果要自定义AFNetworking
的请求参数,可以改AFHTTPSessionManager
->requestSerializer
的相应属性.
NSURLRequest * request
生成后,当然是要拿request
去生成NSURLSessionDataTask
,然后resume
.代码如下(代码中有一些其他操作是用来绑定task
与task
自己的代理(AFURLSessionManagerTaskDelegate
)的,暂时先可以略过,后面会说):
- (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;
}
2.2 管理者
管理者主要负责网络请求流程中的代理方法.
我们的代理方法实现
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
NSLog(@"%@",[error localizedDescription]);
return;
}
NSError *er = nil;
id result = [NSJSONSerialization JSONObjectWithData:_allData options:NSJSONReadingMutableContainers error:&er];
NSLog(@"result: %@", result);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
[_allData appendData:data];
}
AFN的代理方法实现(这些方法由AFHTTPSessionManager的父类AFURLSessionManager实现)
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
NSLog(@"zc 3 AFN NSURLSessionDataDelegate %s",__func__);
//zc read:请求完成
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
NSLog(@"zc 2 AFN NSURLSessionDataDelegate %s",__func__);
//zc read:请求中途
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
}
以上AFHTTPSessionManager
的两个代理方法里都出现了AFURLSessionManagerTaskDelegate
.
由[iOS 移动开发网络 part2]我们已经知道,一个NSURLSession可以包含多个NSURLSessionTask.
NSURLSession可以设置代理,NSURLSessionTask是没有代理的,
多个NSURLSessionTask的情况通知一个'NSURLSession的代理',这样的交互是十分凌乱的.
最好的方式就是每个NSURLSessionTask都有自己的代理,
那么AFURLSessionManagerTaskDelegate就应运而生了.
NSURLSession>NSURLSessionTask 1对多
NSURLSession>AFHTTPSessionManager 1对1
AFHTTPSessionManager>AFURLSessionManagerTaskDelegate 1对多
AFURLSessionManagerTaskDelegate>NSURLSessionDataTask 1对1
那么AFHTTPSessionManager
又是如何持有多个AFURLSessionManagerTaskDelegate
的呢?
AFURLSessionManagerTaskDelegate
与NSURLSessionDataTask
又是怎么绑定的呢?
如图很清晰:
[AFURLSessionManager * manager].mutableTaskDelegatesKeyedByTaskIdentifier
以task的taskIdentifier为键保存着与task对应的AFURLSessionManagerTaskDelegate.
绑定方法task
与AFURLSessionManagerTaskDelegate
的具体方法如下:
这个方法前面已经提过.那会让略过,现在这说
- (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;
}
当然对网络请求流程的掌控不止上面提到的两个代理方法,
AFHTTPSessionManager
的父类AFURLSessionManager
向外暴露了相应的block
来掌控对应的代理方法.如:
重定向
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSURLRequest *redirectRequest = request;
if (self.taskWillPerformHTTPRedirection) {
redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
}
if (completionHandler) {
completionHandler(redirectRequest);
}
}
2.3 返回数据解析模块
我们的数据解析
NSError *er = nil;
id result = [NSJSONSerialization JSONObjectWithData:_allData options:NSJSONReadingMutableContainers error:&er];
NSLog(@"result: %@", result);
AFNetworking负责对返回数据解析的是AFHTTPResponseSerializer
.
AFHTTPResponseSerializer<AFURLResponseSerialization>
AFJSONResponseSerializer : AFHTTPResponseSerializer
AFXMLParserResponseSerializer : AFHTTPResponseSerializer
AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
AFPropertyListResponseSerializer : AFHTTPResponseSerializer
AFImageResponseSerializer : AFHTTPResponseSerializer
AFCompoundResponseSerializer : AFHTTPResponseSerializer
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error;
@end
AFHTTPResponseSerializer
并不做解析数据的具体的事情,子类根据自己支持的数据类型来实现解析数据的协议方法.==>传说中的面向协议编程
.
AFHTTPSessionManager
默认会配备一个[AFJSONResponseSerializer serializer]
.
以下的方法调用栈看的也是AFJSONResponseSerializer
内的实现.
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//验证不通过
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
//没有error 或者 确定是上层校验报的错
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
id responseObject = nil;
NSError *serializationError = nil;
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
// See https://github.com/rails/rails/issues/1742
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length > 0 && !isSpace) {
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
return nil;
}
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
//zc read:检验正常的数据类型和网络状态码
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
AFHTTPRequestSerializer组织请求参数;
AFHTTPSessionManager做为NSURLSession的代理掌控请求的中间流程,并为每一个task保有对应的AFURLSessionManagerTaskDelegate,方便对每一个task做到细致处理;
AFHTTPResponseSerializer做请求到数据的解析.
3.补充
3.1 网络通畅检测模块
- (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);
}
});
}
这个模块很简单,关键代码就是上面这个方法.设置回调给系统的方法,并加入主线程的runloop内,实时监测网络的状态.
而且AFNetworkReachabilityManager
全局是一个单例,我们直接用AFNetworkReachabilityManager
,还是AFNetworking
内部用的都是同一个.
3.2 安全模块
安全模块,因为AFNetworking
与HTTPS挂钩,要大篇幅的介绍HTTPS的验证流程,所以单成一篇==>iOS 移动开发网络 part4:HTTPS.
3.3 UI关联模块
3.3.1 AFNetworkActivityIndicatorManager
首先说说AFNetworkActivityIndicatorManager
,AFNetworking
封装的提示用户网络状态的工具类.
AFNetworkActivityIndicatorManager
有如下状态:
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
//没有请求
AFNetworkActivityManagerStateNotActive,
//请求延迟开始
AFNetworkActivityManagerStateDelayingStart,
//请求进行中
AFNetworkActivityManagerStateActive,
//请求延迟结束
AFNetworkActivityManagerStateDelayingEnd
};
请求延迟开始:小菊花没有开始转,请求开始到请求结束的时间间隔小于延迟时间-->小菊花
不转.
请求延迟结束:小菊花已经开始转,开始转到请求结束时间间隔小于延迟时间-->小菊花
不立马结束.
有这样的延迟操作,小菊花
就不会闪烁个不停.
AFNetworkActivityIndicatorManager
内代码十分清晰,总结成示意图大致是:
- step1:替换系统方法的实现,发广播
AFURLSessionManager.m
内_AFURLSessionTaskSwizzling
的load
方法内:
+ (void)load {
if (NSClassFromString(@"NSURLSessionTask")) {
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];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
+ (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));
}
}
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume];
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
- (void)af_suspend {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_suspend];
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}
- step2:收广播,更改网络请求活跃数
[[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];
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self incrementActivityCount];
}
}
- (void)networkRequestDidFinish:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self decrementActivityCount];
}
}
- step3:根据现有状态和新的活跃数设置新的状态
- (void)updateCurrentStateForNetworkActivityChange {
if (self.enabled) {
switch (self.currentState) {
case AFNetworkActivityManagerStateNotActive:
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingStart];
}
break;
case AFNetworkActivityManagerStateDelayingStart:
//No op. Let the delay timer finish out.
break;
case AFNetworkActivityManagerStateActive:
if (!self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingEnd];
}
break;
case AFNetworkActivityManagerStateDelayingEnd:
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
}
break;
}
}
}
- step4:用新的状态决定小菊花的显示与否,并决定延时定时器(完成延迟+活跃延迟)的开启或者关闭
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
@synchronized(self) {
if (_currentState != currentState) {
[self willChangeValueForKey:@"currentState"];
_currentState = currentState;
switch (currentState) {
case AFNetworkActivityManagerStateNotActive:
[self cancelActivationDelayTimer];
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:NO];
break;
case AFNetworkActivityManagerStateDelayingStart:
[self startActivationDelayTimer];
break;
case AFNetworkActivityManagerStateActive:
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:YES];
break;
case AFNetworkActivityManagerStateDelayingEnd:
[self startCompletionDelayTimer];
break;
}
}
[self didChangeValueForKey:@"currentState"];
}
}
- step5:定时器到时间点再重新设置新的状态
- (void)activationDelayTimerFired {
if (self.networkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
} else {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
- (void)completionDelayTimerFired {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
3.3.2 AFImageDownloader
AFImageDownloader
主要用于图片下载.
UIImageView+AFNetworking.h
- (void)setImageWithURL:(NSURL *)url;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(nullable UIImage *)placeholderImage;
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
UIImageView
会用上面这些方法来加载网络图片,方法内部就调用了AFImageDownloader
.
AFImageDownloader
内部结构大略如上图所示.是有些凌乱,层级也多.不要怕,我们会分模块来说说:
AFImageDownloader
下辖的属性:
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton;
@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;
@property (nonatomic, assign) NSInteger maximumActiveDownloads;
@property (nonatomic, assign) NSInteger activeRequestCount;
@property (nonatomic, strong, nullable) id <AFImageRequestCache> imageCache;
以上三个属性分区
分别对应一个模块:创建下载任务与保存下载任务,多个下载任务执行顺序控制,图片缓存
- 创建下载任务与保存下载任务
UIImageView+AFNetworking.h
内各个设置网路图片方法最终会调用以下方法:
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(nonnull NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
方法内部会创建AFImageDownloader
中最小的下载任务AFImageDownloaderMergedTask
.
作为单例的AFImageDownloader
持有NSMutableDictionary *mergedTasks
.
NSMutableDictionary *mergedTasks
以url
做键保存着AFImageDownloaderMergedTask
.
那么AFImageDownloaderMergedTask
又是什么?
AFImageDownloaderMergedTask
是对NSURLSessionDataTask *task
封装了一层(NSURLSessionDataTask
利用AFHTTPSessionManager
下载图片的过程就不展开说了,与请求十分类似).
AFImageDownloaderMergedTask
还持有一个保存回调的数组==>NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers
.因为一个相同的url有可能出现多个地方的UIImageView
都在加载.
AFImageDownloaderResponseHandler
保存了成功的回调和失败的回调,还有一个NSUUID *uuid
.这个NSUUID *uuid
就是用于匹配对应的UIImageView
的.
UIImageView
调用AFNetworking
加载图片的方法,最终会为UIImageView
绑定一个AFImageDownloadReceipt *af_activeImageDownloadReceipt
.这个AFImageDownloadReceipt
持有:
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSUUID *receiptID;
有了这两个属性就可以让AFImageDownloader
取消对一个UIImageView
的加载回调.
[imageView cancelImageDownloadTask];
- (void)cancelImageDownloadTask {
if (self.af_activeImageDownloadReceipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
[self clearActiveDownloadInformation];
}
}
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
return handler.uuid == imageDownloadReceipt.receiptID;
}];
if (index != NSNotFound) {
AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
[mergedTask removeResponseHandler:handler];
NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
//zc read UIKit 6:没有回调且任务是暂停状态,取消任务
if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[mergedTask.task cancel];
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
字典:[AFImageDownloader]->[NSMutableDictionary *mergedTasks]
键:imageView.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString
得到值:AFImageDownloaderMergedTask
数组:[AFImageDownloaderMergedTask]->NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers
用于匹配的UUID:imageView.receiptID
遍历得到值:AFImageDownloaderResponseHandler
AFImageDownloader
中最小的下载任务是AFImageDownloaderMergedTask
,AFImageDownloaderMergedTask
包含NSURLSessionDataTask
,NSURLSessionDataTask
利用AFHTTPSessionManager
进行下载.为UIImageView
绑定的AFImageDownloadReceipt
使得UIImageView
可以找到自己的回调并予以取消.
- 多个下载任务执行顺序控制
当用于下载的AFImageDownloaderMergedTask
创建好之后,就要考虑是立马开始下载还是入队等待.当最大下载任务数
大于当前下载任务数
则立马开始,否则入队等待.
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
以下的startMergedTask:
方法就是开启一个下载的task.enqueueMergedTask:
方法是用来为入队的task做排序的.
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
[mergedTask.task resume];
++self.activeRequestCount;
}
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritizaton) {
case AFImageDownloadPrioritizationFIFO:
[self.queuedMergedTasks addObject:mergedTask];
break;
case AFImageDownloadPrioritizationLIFO:
[self.queuedMergedTasks insertObject:mergedTask atIndex:0];
break;
}
}
入队有分为:先进先出,后进先出.
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
//先进先出
AFImageDownloadPrioritizationFIFO,
//后进先出
AFImageDownloadPrioritizationLIFO
};
先进先出:加入的task放在数组queuedMergedTasks
的最后面
先进先出:加入的task放在数组queuedMergedTasks
的最前面
- 图片缓存
如上图可以看到AFImageDownloader
有自己的缓存模块id <AFImageRequestCache> imageCache
(AFAutoPurgingImageCache
).
同时AFImageDownloader->AFHTTPSessionManager->NSURLSession
也有缓存模块NSURLCache
.
AFAutoPurgingImageCache
是AFNetworking
自己用的,NSURLCache
是给NSURLSession
用的.那为什么AFNetworking
不也用NSURLCache
来做缓存呢?因为NSURLCache
的诸多限制,例如只支持get请求.没法满足AFNetworking
读写缓存的需求.
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
以上是AFAutoPurgingImageCache
的读写方法,可以加附加的identifier
,没有附加的identifier
就用url做键来保存图片.
4.线程调度
本部分内容会有AFNetworking2.0
的介绍.
4.1 NSURLSession的线程调度甩NSURLConnection几条街
NSURLConnection
- (void)requestByConnection{
_allData = [NSMutableData data];
NSURL *url = [NSURL URLWithString:@"http://c.3g.163.com/photo/api/list/0096/4GJ60096.json"];
NSURLRequest * request = [[NSURLRequest alloc]initWithURL:url];
NSURLConnection * connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
[connection start];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[_allData appendData:data];
NSLog(@"zc NSThread %@",[NSThread currentThread]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSError *er = nil;
id result = [NSJSONSerialization JSONObjectWithData:_allData options:NSJSONReadingMutableContainers error:&er];
NSLog(@"zc NSThread %@",[NSThread currentThread]);
}
打印:
zc NSThread <NSThread: 0x60800027c9c0>{number = 1, name = (null)}
zc NSThread <NSThread: 0x60800027c9c0>{number = 1, name = (null)}
NSURLSession
- (void)requestBySession{
_allData = [NSMutableData data];
NSURLSessionConfiguration *configura = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configura delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:@"http://c.3g.163.com/photo/api/list/0096/4GJ60096.json"];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url];
[dataTask resume];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSError *er = nil;
id result = [NSJSONSerialization JSONObjectWithData:_allData options:NSJSONReadingMutableContainers error:&er];
NSLog(@"zc NSThread %@",[NSThread currentThread]);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
[_allData appendData:data];
NSLog(@"zc NSThread %@",[NSThread currentThread]);
}
打印:
zc NSThread <NSThread: 0x60800027c9c0>{number = 5, name = (null)}
zc NSThread <NSThread: 0x60800027c9c0>{number = 5, name = (null)}
以上是最简单的拿NSURLConnection
和NSURLSession
请求数据时,代理方法调用时的线程信息.NSURLConnection
全程都在主线程,NSURLSession
全程都在次线程.如此可见一斑==>NSURLSession
的线程调度甩NSURLConnection
几条街.
AFNetworking2.0
封装NSURLConnection
.
AFNetworking3.0
封装NSURLSession
.
而我们用起来却没什么差别,证明AFNetworking2.0
较AFNetworking3.0
的线程调度会更有难度.
4.2 NSURLConnection VS NSRunLoop
一般来说我们直接用NSURLConnection
在主线程上开异步请求也没什么大问题,但请求一多,返回数据也在主线程解析对UI的体验就有影响了.
上面的方法可以改进为:主线程发请求,收到数据后,开辟次线程解析.
同时我的第一直觉又想出一招:任意开一个次线程发请求,再任意开一个次线程解析数据.
我还写了代码试了试:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURL *url = [NSURL URLWithString:@"http://c.3g.163.com/photo/api/list/0096/4GJ60096.json"];
NSURLRequest * request = [[NSURLRequest alloc]initWithURL:url];
NSURLConnection * connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
[connection start];
NSLog(@"zc NSThread 1 %@",[NSThread currentThread]);
});
搞笑的是代理方法全都没有调用,怎么回事?
点进NSURLConnection
,看到这个方法:
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode;
才知道NSURLConnection
的运行载体是runLoop
.NSURLConnection
要么跑主线程的runLoop
,要么跑次线程的runLoop
,反正必须选一个runLoop
,而且这个runLoop
必须一直跟着请求的流程一直存活(这也就意味着runLoop
对应的线程也必须跟着请求的流程一直存活),否则NSURLConnection
就无法回调代理方法.
这也是很好的回答下面的两个问题:
为什么NSURLConnection的默认实现只能在主线程运行? 主线程一直存活
为什么我用NSURLConnection在次线程发请求,代理方法不回调? 发请求的次线程早死了
方案1.1:
主线程发请求,收到数据后,主线程解析.
请求过多时,影响UI体验.不好
方案1.2:
主线程发请求,收到数据后,开辟次线程解析.
这样多个请求的数据就对应多个次线程,多个次线程有性能损耗,线程间切换也带来的损耗.有瑕疵
方案2:
多个次线程发多个请求,保证多个次线程存活,收到数据后,对应次线程解析.
阻塞多个次线程.很瞎
方案3:
用特定的次线程发多个请求,保证特定线程存活,特定的次线程用于返回数据的解析.
✔️
AFNetworking2.0
也正是用的方案3.保证特定线程存活的代码如下:
+ (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;
}
上面的两个方法给runLoop
加NSMachPort
保证线程存活,早就被奉为传世经典,我就不多做讲解了.
4.3 NSOperation VS NSOperationQueue
AFNetworking2.0
内的各大模块的关系大概如上.
最小下载任务AFHTTPRequestOperation
继承自NSOperation
.我们调用请求方法创建AFHTTPRequestOperation
完成后,AFHTTPRequestOperation
被加入AFHTTPRequestOperationManager
下辖的NSOperationQueue
中.NSOperationQueue
根据自己的任务并发数
来确定任务何时执行,这个任务并发数
我们是可以在外围设置的.
AFNetworking2.0
的常驻线程完成所有网络请求是AFNetworking
线程调度最精彩的一笔;
AFNetworking2.0
最小下载任务AFHTTPRequestOperation
继承自NSOperation
,再加入NSOperationQueue
中,也是间接的线程调度.
AFNetworking2.0
与AFNetworking3.0
细小处都有锁
,dispatch_group_t
,dispatch_queue_t
等等,我就不一一举例了哈.
5.AFNetworking功绩
5.1线程调度
刚说完,看上面.
5.2细化任务跟踪粒度
说AFNetworking3.0
时候已经说过,多个task走一个NSURLSession
的代理方法粒度实在太大了,而AFNetworking3.0
就真的做到了为每一task分配一个代理.
5.3请求组装+数据解析 方法协助
以下是NSURLSession
上传多张照片的代码和AFNetworking3.0
上传多张照片的代码的对比,请自行感受.
- (void)sendImagesByURLSession
{
NSString *URLString = @"http://192.168.8.11/upload.php";
NSString *serverFileName = @"zc[]";
NSString *filePath1 = [[NSBundle mainBundle] pathForResource:@"on_show_1.png" ofType:nil];
NSString *filePath2 = [[NSBundle mainBundle] pathForResource:@"on_show_2.png" ofType:nil];
NSArray *filePaths = @[filePath1,filePath2];
NSDictionary *textDict = @{@"kkk":@"vvv"};
[self uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:filePaths textDict:textDict];
}
- (void)uploadFilesWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict
{
NSURL *URL = [NSURL URLWithString:URLString];
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
[requestM setValue:@"multipart/form-data; boundary=itcast" forHTTPHeaderField:@"Content-Type"];
requestM.HTTPMethod = @"POST";
requestM.HTTPBody = [self getHTTPBodyWithServerFileName:serverFileName filePaths:filePaths textDict:textDict];
[[[NSURLSession sharedSession] dataTaskWithRequest:requestM completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error == nil && data != nil) {
NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
NSLog(@"44");
} else {
NSLog(@"%@",error);
}
}] resume];
}
- (NSData *)getHTTPBodyWithServerFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict
{
NSMutableData *dataM = [NSMutableData data];
[filePaths enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSMutableString *stringM = [NSMutableString string];
[stringM appendString:@"--itcast\r\n"];
[stringM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",serverFileName,[obj lastPathComponent]];
[stringM appendString:@"Content-Type: image/png\r\n"];
[stringM appendString:@"\r\n"];
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
[dataM appendData:[NSData dataWithContentsOfFile:obj]];
[dataM appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
[textDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSMutableString *stringM = [NSMutableString string];
[stringM appendString:@"--itcast\r\n"];
[stringM appendFormat:@"Content-Disposition: form-data; name=%@\r\n",key];
[stringM appendString:@"\r\n"];
[stringM appendFormat:@"%@\r\n",obj];
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
}];
[dataM appendData:[@"--itcast--" dataUsingEncoding:NSUTF8StringEncoding]];
return dataM.copy;
}
- (void)sendImagesByAFN{
NSString *filePath1 = [[NSBundle mainBundle] pathForResource:@"on_show_1.png" ofType:nil];
NSString *filePath2 = [[NSBundle mainBundle] pathForResource:@"on_show_2.png" ofType:nil];
NSData * data1 = [NSData dataWithContentsOfFile:filePath1];
NSData * data2 = [NSData dataWithContentsOfFile:filePath2];
NSArray * attributeDataArr = @[@{@"data":data1,@"name":@"zc[]",@"fileName":@"on_show_1",@"mimeType":@"image/png"},
@{@"data":data2,@"name":@"zc[]",@"fileName":@"on_show_2",@"mimeType":@"image/png"}];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:@"http://192.168.8.11/upload.php" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
for (NSDictionary * attributeData in attributeDataArr)
{
[formData appendPartWithFileData:attributeData[@"data"]
name:attributeData[@"name"]
fileName:attributeData[@"fileName"]
mimeType:attributeData[@"mimeType"]];
}
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
}
5.4安全验证 方法协助
以自签名证书为例,我们只需要给AFSecurityPolicy
设置一份本地的证书.而如果自己实现证书的遍历+验证等工作,还得与SecPolicy.framework
里的API打交道,不是一般人能做到的.
5.5.太多了......
感谢大牛们的贡献,让我(们)可以像白痴一样和网络请求打交道那么久!!!😄 😄 😄 .但现在我不想当白痴了.