探秘AFNetworking

53次阅读

共计 39798 个字符,预计需要花费 100 分钟才能阅读完成。

该文章属于 < 简书 — 刘小壮 > 原创,转载请注明:

< 简书 — 刘小壮 >


AFNetworking 源码分析

AFNetworkingiOS 最常用的网络框架,虽然系统也有 NSURLSession,但是我们一般不会直接用它。AFNetworking 经过了三个大版本,现在用的大多数都是 3.x 的版本。

AFNetworking经历了下面三个阶段的发展:

  • 1.0 版本 : 基于 NSURLConnection 的封装。
  • 2.0 版本 : 两套实现,分别基于 NSURLConnectionNSURLSession,是转向 NSURLSession 的过渡版。
  • 3.0 版本 : 基于 NSURLSession 的封装。

文件构成

AFNetworking3.X的构成很简单,主要就四部分,除此之外还有一些基于 UIKitCategory,但这些并不是标配。

  • Manager : 负责处理网络请求的两个 Manager,主要实现都在AFURLSessionManager 中。
  • Reachability : 网络状态监控。
  • Security : 处理网络安全和 HTTPS 相关的。
  • Serialization : 请求和返回数据的格式化器。

AFURLSessionManager

AFN3.0 中,网络请求的 manager 主要有 AFHTTPSessionManagerAFURLSessionManager构成,二者为父子关系。这两个类职责划分很清晰,父类负责处理一些基础的网络请求代码,并且接受 NSURLRequest 对象,而子类则负责处理和 http 协议有关的逻辑。

AFN的这套设计很便于扩展,如果以后想增加 FTP 协议的处理,则基于 AFURLSessionManager 创建子类即可。子类中只需要进行很少的代码处理,创建一个 NSURLRequest 对象后调用父类代码,由父类去完成具体的请求操作。

创建 sessionManager

AFHTTPSessionManager类的初始化方法中并没有太多实现代码,其内部调用的都是父类 AFURLSessionManagerinitWithSessionConfiguration方法,下面是此方法内部的一些关键代码。

在初始化方法中包含一个参数sessionConfiguration,如果没有传入的话默认是使用系统的defaultConfiguration,我们创建是一般都不会自定义configuration,所以大多数都是系统的。

if (!configuration) {configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}

随后是 NSURLSession 的初始化代码,关于 NSOperationQueue 后面详细进行讲解。NSURLSession的初始化方式有两种,一种是使用系统的共享 session,另一种是自己创建sessionAFN 选择的是创建自己的session,并且每个请求都会创建一个独立的session

可以通过 NSURLSession 进行连接复用,这样可以避免很多握手和挥手的过程,提高网络请求速度,苹果允许 iOS 设备上一个域名可以有四个连接同时存在。但是由于 AFN 的实现是每个请求都创建一个session,所以就不能进行连接复用。

所以可以通过在外面对 AFN 进行二次封装,将 AFHTTPSessionManager 复用为单例对象,通过复用 sessionManager 的方式,来进行连接的复用。但是这种方案对于不同的 requestSerializerresponseSerializer 等情况,还是要做特殊兼容,所以最好建立一个 sessionManager 池,对于同类型的 sessionManager 直接拿出来复用,否则就创建新的。

// 共享 session 连接池
[NSURLSession sharedSession];
// 创建新 session,则不能使用共享 session 连接池
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

由于当前 AFURLSessionManager 对象的所有 sessionTask 请求任务,都是共享同一个回调代理的,所以 AFN 为了区分每个 sessionTask,通过下面的可变字典,将所有taskDelegatetask.taskIdentifier的进行了一一对应,以便于很容易的对每个请求 task 进行操作。

self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

在初始化方法中,可以发现 AFN 在创建 session 后,调用了 getTasksWithCompletionHandler 方法来获取当前所有的 task。但是现在刚创建session,理论上来说是不应该有task 的。但从 AFNissues中找到了答案 issues 3499。

这是因为,在 completionHandler 回调中,为了防止进入前台时,通过 session id 恢复的 task 导致一些崩溃问题,所以这里将之前的 task 进行遍历,并将回调都置nil

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {for (NSURLSessionDataTask *task in dataTasks) {[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
    }

    for (NSURLSessionUploadTask *uploadTask in uploadTasks) {[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
    }

    for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
    }
}];

创建 task

AFURLSessionManager 中进行 task 的创建,task的类型总共分为三种,dataTaskuploadTaskdownloadTaskAFN并没有对 streamTask 进行处理。

AFHTTPSessionManager在创建 GETPOST 等请求时,本质上都是调用了下面的方法或其类似的方法,方法内部会创建一个 task 对象,并调用 addDelegateForDataTask 将后面的处理交给 AFURLSessionManagerTaskDelegate 来完成。随后会将 task 返回给调用方,调用方获取到 task 对象后,也就是子类 AFHTTPSessionManager,会调用resume 方法开始请求。

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

除了普通请求外,uploaddownload都有类似的处理。

addDelegateForDataTask 方法中,会调用 sessionManagersetDelegate:forTask:方法,此方法内部将 tasktaskDelegate进行了注册。由于 AFN 可以通过通知让外界监听请求状态,所以在此方法中还监听了 taskresumesuspend 事件,并在实现代码中将事件广播出去。

- (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,因为确实NSURLSessionAPI封装程度比较好,也很好使用。

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 属性,并且实现 cancelsuspend 等状态回调。任务的状态和进度处理交给 NSProgress,在回调方法中直接拼接NSProgress 的进度,从而回调 KVO 方法。

NSProgress内部的 cancelpauseresume 方法,正好可以对应到 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,将dataTaskresumesuspend 方法进行替换,并且在替换后的方法中发出对应的通知,并没有太多实际的功能。

只不过 taskSwizzling 类中还是有一些不错的代码设计值得借鉴的,由于 sessionTask 存在一系列继承链,所以直接对其进行 swizzling 对其他子类并不生效,因为每个子类都有自己的实现,而写一大堆 swizzling 又没有什么技术含量。

iOS7iOS8上,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为了避免发生编译器警告,采取了预编译指令对代码进行修饰,预编译指令基本由三部分组成,pushpopignored类型。Github上有人维护了一份 clang warning 清单,如果想进行对应的预编译处理可以上去找找有没有合适的。

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

线程问题

NSURLSessioniOS8 以下会并发创建多个 task,但并发设置task identifier 的时候会存在 identifier 重复的问题。为了解决这个问题,在 iOS8 以下,系统将所有 sessionTask 的创建都放在一个同步的串行队列中进行,保证创建及赋值操作是串行进行的。

url_session_manager_create_task_safely(^{dataTask = [self.session dataTaskWithRequest:request];
});

url_session_manager_create_task_safely(^{uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});

// 如果 Foundation 版本小于 iOS8,则把 block 任务放在一个同步队列中执行。这个问题是由于在 iOS8 以下并发创建任务,可能会有多个相同的 identifier
static void url_session_manager_create_task_safely(dispatch_block_t block) {if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {dispatch_sync(url_session_manager_creation_queue(), block);
    } else {block();
    }
}

一个比较有意思的是,AFN为了让开发者明白为什么要加这个判断,对 iOS8 系统的判断定义成了一个宏,并且用 Apple Supportid作为宏定义命名,很见名知意。

#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0

AFN在回调 didCompleteWithError 方法,并处理返回数据时,会切换到其他线程和 group 去处理,处理完成后再切换到主线程并通知调用方。

AFN提供了两个属性,用来设置请求结束后进行回调的 dispatch queuedispatch group,如果不设置的话,AFN会有默认的实现来处理请求结束的操作。下面是 groupqueue的实现,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在创建 AFURLSessionManageroperationQueue时,将其最大并发数设置为 1。这是因为在创建 NSURLSSession 时,苹果要求网络请求回来的数据顺序执行,为了保证代理方法的执行顺序,所以需要串行的调用 NSURLSSession 的代理方法。

AFHTTPSessionManager

AFHTTPSessionManager本质上是对父类 AFURLSessionManager 的封装,主要实现都在父类中,自己内部代码实现很简单。在创建 AFHTTPSessionManager 时会传入一个 baseURL,以及指定requestSerializerresponseSerializer 对象。

从代码实现来看,AFN的请求并不是单例形式的,每个请求都会创建一个新的请求对象。平时调用的 GETPOST 等网络请求方法,都定义在 AFHTTPSessionManager 中。AFHTTPSessionManager内部则调用父类方法,发起响应的请求并获取到 task 对象,调用 taskresume后返回给调用方。

- (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 格式请求。

这三个类区别就在于 Content-Type 不同,其他基本都是一样的。AFN默认是 HTTP 的。

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 添加参数,AFNrequestManager 并不直接对外提供设置请求头的代码。通过 requestSerializer 可以对请求头进行添加和删除、以及清空的操作。

从创建 AFURLRequestSerialization 对象到最后返回 NSURLRequest 对象,中间的过程并不复杂,主要是设置请求头和拼接参数,逻辑很清晰。

AFQueryStringPair

AFURLRequestSerialization有一个很重要的功能就是参数处理,AFQueryStringPair就是负责处理这些参数的。pair类中定义了两个属性,分别对应请求参数的keyvalue。除此之外,还定义了一些非常实用的 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方法实现很简单,就是进行一个 keyvalue 的拼接,并且在中间加上“=”。

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 对象的类型,但之前的层级结构不变。这句话是什么意思呢,就是说对原有传入的对象进行逐层递归调用,并且将最后一层字典的 keyvalue 参数,转成 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) {// nothing} success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {// nothing} failure:nil];

进行表单提交时,可以直接传入文件,也可以传入路径。表单提交可以同时提交多个文件,理论上数量不受限制。

缓存策略

AFN的缓存策略和 NSURLCache 的缓存策略一致,并且直接使用系统的枚举,这对 iOS 开发者是非常友好的。下面是枚举定义,忽略掉一些 unimplemented 的,和一些重定向到已有枚举的,可用的都在这。

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0,
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    NSURLRequestReturnCacheDataElseLoad = 2,
    NSURLRequestReturnCacheDataDontLoad = 3,
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
    NSURLRequestReloadRevalidatingCacheData = 5,
};
  • NSURLRequestUseProtocolCachePolicy,使用协议指定的缓存策略。
  • NSURLRequestReloadIgnoringLocalCacheData,忽略缓存,直接发起请求。
  • NSURLRequestReturnCacheDataElseLoad,不验证缓存过期时间,如果有则使用缓存数据,如果不存在则请求服务器。
  • NSURLRequestReturnCacheDataDontLoad,不验证缓存过期时间,如果有则使用缓存数据,如果不存在则请求失败。
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData,忽略本地缓存,以及代理等中间介质的缓存。
  • NSURLRequestReloadRevalidatingCacheData,和数据的源服务器验证数据合法性,如果可以用就直接使用缓存数据,否则从服务器请求数据。

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;
}

AFNetworking 的设计技巧

bundleForClass

在使用 NSBundle 对象时,我们最常用的就是 mainBundle 或者 bundleWithPath 这种方式获取 bundle,这种对于都是从app 二进制读取的时候是没有问题的。但是如果涉及到 framework 动态库,就不是那么易于使用。

framework中可以包含资源文件,例如 .bundle 文件。如果是动态库形式的 framework(framework 也有静态形式),其会以一个独立二进制的形式表现,并且会分配独立的二进制空间。在读取 bundle 的时候,就可以考虑使用 bundleForClass 的方式读取。

bundleForClass表示从当前类定义的二进制,所在的程序包中读取 NSBundle 文件。例如 .app 就是从 main bundle 中读取,如果是 framework 就从其所在的二进制中读取。

网络指示器

AFN提供了一些 UIKitCategory,例如网络请求发起时,网络指示器转菊花,则由 AFNetworkActivityIndicatorManager 类负责。开启网络指示器很简单,添加下面代码即可,网络指示器默认是关闭的。

[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];

这里不对 AFNetworkActivityIndicatorManager 的代码进行过多的分析,只是调其中比较重要的点来分析,下面统称为indicatorManager

之前在 _AFURLSessionTaskSwizzling 类中写了很多代码,就是为了发出 resumesuspend两个通知,这两个通知在 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 的方式加锁,防止从各个线程调用过来的通知造成资源抢夺的问题。

AFSecurityPolicy

验证处理

AFN支持 https 请求,并通过 AFSecurityPolicy 类来处理 https 证书及验证,但其 https 请求的执行还是交给 NSURLSession 去完成的。

下面是 NSURLSession 的一个代理方法,当需要进行证书验证时,可以重写此方法并进行自定义的验证处理。验证完成后通过 completionHandlerblock来告知处理结果,并且将验证结果 disposition 和公钥 credential 传入。

AFN通过 AFSecurityPolicy 类提供了验证逻辑,并且在内部可以进行证书的管理。也可以不使用 AFN 提供的验证逻辑,重写 sessionDidReceiveAuthenticationChallengeblock即可自定义验证逻辑,不走 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 也有对应的代理方法。两个代理方法内部实现基本一样,区别在于对于每个 taskAFN 提供了 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>

AFSecurityPolicy 概述

AFSecurityPolicy的职责比较单一,只处理公钥和验证的逻辑,其定义是一个单例对象。此类主要由四个属性和一个方法构成。

// 证书验证方式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 本地自签名证书集合
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否验证证书的合法性(是否允许自签名证书)
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否验证域名是否有效
@property (nonatomic, assign) BOOL validatesDomainName;

如果进行细分的话,AFSecurityPolicy的功能基本就两个。一个是通过 CA 的方式进行验证,另一个就是进行 SSL Pinning 自签名验证。evaluateServerTrust:forDomain:AFSecurityPolicy 最主要的方法,用来进行证书的合法性验证。

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 的证书。

进行沙盒验证时,需要将 AFSecurityPolicyallowInvalidCertificates设置为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 2.x

AFNetworking2.xNSURLSessionNSURLConnection两部分组成,并且分别对应不同的类,这里主要介绍 NSURLConnection 部分的源码实现。

总体概览

NSURLConnection实现由下面三个类构成,从源码构成可以看出,无论是 session 还是 connection 方案,都具有很好的扩展性。例如这里 AFHTTPRequestOperation 是基于 AFURLConnectionOperation 实现的,如果需要实现 FTP 协议,则可以创建一个继承自 AFURLConnectionOperationAFFPTConnectionOperation类并重写对应方法即可。

  • AFURLConnectionOperation
    继承自 NSOperation,负责网络请求的逻辑实现,每个网络请求就是一个Operation 对象。
  • AFHTTPRequestOperation
    继承自 AFURLConnectionOperation,处理HTTP 相关网络请求。
  • AFHTTPRequestOperationManager
    内部持有一个 NSOperationQueue,负责管理所有Operation 网络请求。

AFURLConnectionOperation

下面是 AFURLConnectionOperation 的初始化方法,和 AFURLSessionManager 有些不一样。其内部增加了状态的概念,以及 RunloopMode 的概念,这两个我们后面会详细讲解。shouldUseCredentialStorage表示是否由系统做证书验证,后面设置了 securityPolicy,和sessionManager 一样也是使用默认方案。

- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    _state = AFOperationReadyState;

    self.lock = [[NSRecursiveLock alloc] init];
    self.lock.name = kAFNetworkingLockName;
    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest;
    self.shouldUseCredentialStorage = YES;
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
}

AFURLConnectionOperation继承自 NSOperation,由于NSOperation 和网络请求的过程很像,有开始、暂停、完成等,并且很好的支持 KVO 监听,所以 AFN 将每个网络请求都当做一个 Operation 任务。AFURLConnectionOperation可以设置任务优先级,也可以通过 AFHTTPRequestOperationManager 设置最大并发数,基本上 NSOperationQueue 提供的功能都能用。

AFHTTPRequestOperationManager 中定义了 NSOperationQueue,创建网络请求任务后,都会被加入到queue 中,随后由系统调用 queue 中的 operation 任务,执行 operationstart方法发起请求。AFURLConnectionOperation只需要在内部实现 startpauseresume 等父类方法即可,其他都由系统去进行调用。这种设计可以很好的将 manageroperation进行解耦,二者不用直接发生调用关系。

NSURLConnection中,每个网络请求对应一个 AFHTTPRequestOperation,所有网络请求都共用一个manager 来管理 operation。而AFHTTPSessionManager 则不同,每个网络请求对应一个 manager 以及一个task

state 设计

AFURLConnectionOperation支持 KVO 的方式,让外界监听网络请求的变化,并通过重写 setState 方法,在内部加入 willChangeValueForKey 触发 KVO 回调。AFN通过 AFOperationState 来管理网络请求状态,下面是 AFN 对其的状态定义。

  • AFOperationPausedState
    请求暂停
  • AFOperationReadyState
    请求已准备好
  • AFOperationExecutingState
    请求正在执行中
  • AFOperationFinishedState
    请求完成

当网络请求状态发生改变时,都会调用 setState 方法进行赋值,例如下面是请求完成时的处理代码。除此之外,当判断 AFN 请求状态时,也是通过这个属性作为判断依据的。

- (void)finish {[self.lock lock];
    self.state = AFOperationFinishedState;
    [self.lock unlock];
}

常驻线程

AFURLConnectionOperation中设计了常驻线程,并且重写了 operationstart等方法,网络请求的 startcancelpause 等操作,都是在常驻线程中完成的。网络请求结束后,数据回调的处理也是在这个线程中完成的。

这是因为在哪个线程创建 NSURLConnection 对象并发出请求,则数据返回时也默认从那个线程接受数据。如果请求都是从主线程发出的,请求返回时如果屏幕正在滑动,runloopModeUITrackingRunLoopMode 则不能处理返回数据。而如果把网络请求都加到主线程的 NSRunLoopCommonModes 中,在大量网络请求返回时,处理返回数据会影响屏幕滑动FPS

所以为了保证网络请求数据可以正常返回并被处理,而又不影响屏幕 FPS,则用一个单独的线程来处理。如果每个请求都对应一个线程来处理返回任务,会造成大量线程的占用,所以用一个常驻线程来处理所有网络请求,来保证线程资源的最小占用。常驻线程实际上是一个单例线程,并且这个单例线程被加入了一个Port 进行保活,保证线程可以不被退出。

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {[[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}

通过 AFURLConnectionOperation 发起网络请求时,实际创建 connection 对象的代码在下面的方法中。在创建 connection 对象后,并没有立即发出网络请求,而是将 startImmediately 设置为 NO。随后会设置NSURLConnectionNSOutputStreamRunloopMode,网络请求会从单例线程的runLoopModes 中发出,这样当网络请求返回时,回调代码也会在 runLoopModes 中去执行。

operationDidStart方法中会调用 NSURLConnectionscheduleInRunLoop:forMode:方法,将网络请求任务派发到 Runloop 指定的 Mode 中。我觉得给 Operation 设置 runLoopModes 其实意义不大,因为常驻线程基本上只会有一个 Mode,也就是NSRunloopDefaultMode,基本上不会有其他Mode,所以这里设置runLoopModes 没什么意义。

- (void)operationDidStart {self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    for (NSString *runLoopMode in self.runLoopModes) {[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
        [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
    }

    [self.outputStream open];
    [self.connection start];
}

代理方法

AFURLConnectionOperation是通过 NSURLConnection 实现网络请求的,这里简单讲一下 operation 中代理方法的实现。

AFN实现了 https 证书验证的代码,具体实现和 AFURLSessionManager 基本类似,并且也是通过 AFSecurityPolicy 来处理具体的证书验证逻辑。

- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

关于请求服务器数据这块值得讲一下,在 NSURLConnection 接收服务器数据时,AFN通过创建了一个 outputStream,来承载和组织具体的数据,并且在内存中进行存储。当没有可用空间或发生其他错误时,会通过streamError 的方式进行体现。

当网络请求结束时,会调用 didFinishLoading 方法,AFN会从 outputStream 中拿出数据并赋值给responseData,当做返回值数据使用。

- (void)connection:(NSURLConnection __unused *)connection
    didReceiveData:(NSData *)data
{NSUInteger length = [data length];
    while (YES) {
        NSInteger totalNumberOfBytesWritten = 0;
        if ([self.outputStream hasSpaceAvailable]) {const uint8_t *dataBuffer = (uint8_t *)[data bytes];

            NSInteger numberOfBytesWritten = 0;
            while (totalNumberOfBytesWritten < (NSInteger)length) {numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
                if (numberOfBytesWritten == -1) {break;}

                totalNumberOfBytesWritten += numberOfBytesWritten;
            }

            break;
        } else {[self.connection cancel];
            if (self.outputStream.streamError) {[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
            }
            return;
        }
    }

    if (self.downloadProgress) {self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
    }
}

- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

    [self.outputStream close];
    if (self.responseData) {self.outputStream = nil;}

    self.connection = nil;

    [self finish];
}

outputStream,也会有与之对应的inputStreaminputStream 实现很简单,就是修改 NSMutableURLRequestHTTPBodyStream

- (NSInputStream *)inputStream {return self.request.HTTPBodyStream;}

- (void)setInputStream:(NSInputStream *)inputStream {NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
    mutableRequest.HTTPBodyStream = inputStream;
    self.request = mutableRequest;
}

内存管理

在创建 AFHTTPRequestOperation 时会将 successfailureblock 传给 operation,并且在operation 执行完成并回调 completionBlock 时,执行这两个 block 代码。但是由于 completionBlock 中直接使用了self,导致了循环引用的问题。

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id __nullable responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
      self.completionBlock = ^{// something code ...};
}

completionBlock的循环引用是 AFN 有意而为之的,为的就是保持 operation 的生命周期,以保证请求处理完成并接收返回的 block 回调。

对于循环引用的生命周期,AFN采取的是主动打破循环引用的方式,也就是重写父类的 completionBlock,并且在调用block 结束后,主动将 completionBlock 赋值为nil,从而主动打破循环引用。

- (void)setCompletionBlock:(void (^)(void))block {if (!block) {[super setCompletionBlock:nil];
    } else {__weak __typeof(self)weakSelf = self;
        [super setCompletionBlock:^ {__strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop

            dispatch_group_async(group, queue, ^{block();
            });

            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{[strongSelf setCompletionBlock:nil];
            });
        }];
    }
}

AFNetworkReachabilityManager

AFNetworking中还有很重要的一部分,就是 Reachability,用来做网络状态监控的。AFNetworkingYYKit、苹果官方都提供有ReachabilityAPI使用,内部实现原理基本差不多。

代码实现也很简单,主要依赖 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 函数,回调有三个参数。targetSCNetworkReachabilityRef 对象,flags是网络状态,info是我们设置的 block 回调参数。回调 Callback 函数后,内部会通过 block 以及通知的形式,对外发出回调。

- (void)startMonitoring {[self stopMonitoring];

    if (!self.networkReachability) {return;}

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {__strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {strongSelf.networkReachabilityStatusBlock(status);
        }

    };

    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

- (void)stopMonitoring {if (!self.networkReachability) {return;}

    SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}

AFNetworking 总结

AFNetworking对请求数据的序列化,以及返回数据的反序列化做了很多处理。使开发者只需要传入一个字典即可构建请求参数,无需处理拼接到 URL 后面或参数转换为 body 二进制的细节,这些都由 AFNetworking 内部处理并进行容错,开发者只需要指定请求方式即可。

通过 AFNetworking 实现 https 也很方便,AFSecurityPolicy可以很好的管理 CA 以及自签名证书,以及处理 https 请求过程中的一些逻辑,我们只需要告诉 AFNetworking 怎么处理即可,如果不指定处理方式则使用默认 CA 证书的方式处理。

AFNetworking对于后台下载以及断点续传有很好的支持,我们可以在 AFNetworking 的基础上,很简单的就完成一个下载模块的设计。如果自己写后台下载和断点续传的代码,工作量还是不小的。

并且 AFNetworking 在网络库的设计上还提供了很强的自定义性,例如指定证书、URL缓存处理,以及下载过程中不同下载阶段的处理。如果没有提供自定义处理,则使用默认处理方式。

正文完
 0