AF版本基于3.0,下面將從使用切入開始分析。
1.使用
例子:
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
session.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json" ,@"text/javascript", nil];
session.responseSerializer = [AFHTTPResponseSerializer serializer];
[session GET:@"https://www.baidu.com"
parameters:nil
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"請求失敗");
}];
2 代碼分析
2.1 [AFHTTPSessionManager manager]
AFHTTPSessionManager
is a subclass of AFURLSessionManager
with convenience methods for making HTTP requests. When a baseURL
is provided, requests made with the GET
/ POST
/ et al. convenience methods can be made with relative paths.
我們加入斷點查看一下manager的初始化調用棧如下圖:
進入到AFURLSessionManager
中的manager中分析:
// 1.設置全局的網絡行為策略的配置
self.sessionConfiguration = configuration;
// 2.設置請求的隊列,默認最大的并發數為1
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// 3.根據configuration,operationQueue初始化全局的NSURLSession
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
// 4.設置網絡請求響應的數據解析實例
self.responseSerializer = [AFJSONResponseSerializer serializer];
// 5.設置網絡請求安全策略實例(后續針對這個做具體說明)
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
// 6.初始化全局的網絡狀態監聽的實例
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 7.將taskId與其delegate綁定,實現解耦,后續對整個過程做分析
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
2.2.全局的網絡行為策略的配置 NSURLSessionConfiguration
An NSURLSessionConfiguration object defines the behavior and policies to use when uploading and downloading data using an NSURLSession object. When uploading or downloading data, creating a configuration object is always the first step you must take. You use this object to configure the timeout values, caching policies, connection requirements, and other types of information that you intend to use with your NSURLSession object.
簡單羅列為一下幾點:
- NSURLSessionConfiguration 可以控制網絡請求中的緩存策略,超時設置等。
- 如果需要更改網絡請求的行為策略必須重新在更改NSURLSessionConfiguration后再創建一個新的NSURLSession對象。
2.3 請求的方法GET:parameters:process:success:failure:
2.3.1
具體調用棧如下圖:
這里引入了一個新的類:NSURLSessionDataTask
,繼承了NSURLSessionTask
,我們看看官方的說明:
The NSURLSessionTask class is the base class for tasks in a URL session. Tasks are always part of a session; you create a task by calling one of the task creation methods on an NSURLSession object. The method you call determines the type of task.
URL sessions provide three types of tasks: data tasks, upload tasks, and download tasks. These tasks are instances of the NSURLSessionDataTask, NSURLSessionUploadTask, NSURLSessionDownloadTask, NSURLSessionStreamTask subclasses of NSURLSessionTask, respectively.
簡單羅列為一下幾點:
- NSURLSessionTask是官方提供的幾種網絡任務類的基類。
- 官方提供了三種任務處理的子類:簡單數據處理任務類,上傳任務類,下載任務類。
介紹了這其中使用到的核心類NSURLSessionTask,我們由底層向上看一下具體的調用流程。
2.3.2 [AFHTTPSessionManager dataTaskWithHTTPMethod:...]
// 1.通過全局配置的requestSerializer 初始化一個請求的實例
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
// 2.根據請求的實例再初始化一個task的實例
__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);
}
}
}];
2.3.2 task與delegate的綁定[AFURLSessionManager addDelegateForDataTask:...]
這里是初始化一個請求task的基本步驟:
// 1.先根據請求信息初始化一個task的實例
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[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
{
// 2.初始化一個任務的代理
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
// 此處賦值了請求完成的回調,后續會用到
delegate.completionHandler = completionHandler;
// 2.利用全局的字典存儲綁定信息,key為taskId,value為代理的實例
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
// 1.綁定taskId與代理self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// 2.使用kvo對一些方法監聽,返回上傳或者下載的進度
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
2.3.3 URLSession:task:didCompleteWithError:
當請求收到了響應后,會觸發該回調,我們分析一下他具體的處理。
// AFURLSessionManager.m
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
// 1.根據task獲取綁定的代理實例
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
// 2.統一處理
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
// AFURLSessionManagerTaskDelegate.m
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
// 1.iOS網絡框架返回的錯誤信息處理
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
// 2.請求成功后需要用響應的數據解析類實例處理返回數據,同樣如果出現錯誤則回調上層
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
// 3.綁定task與處理的數據代理時,傳入代理的完成的回調
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
#pragma clang diagnostic pop
}
3 調試中遇到的問題的解決
1.請求收到響應:response Code=-1016 "Request failed: unacceptable content-type: text/html"
代碼段:
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
[session GET:@"https://www.baidu.com"
parameters:nil
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"請求失敗");
}];
解決:響應加入對text/html
格式的支持
session.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json" ,@"text/javascript", nil];
2.Code=3840 "JSON text did not start with array or object and option to allow fragments not set."
解決:AF默認設置的響應的解析類型為json,因此需要改變解析類型。
解決:
session.responseSerializer = [AFHTTPResponseSerializer serializer];
3.AFNetworking 與 RunLoop 之間的關系,3.0為什么不需要加入如下的段?
我們看看NSURLConnection 的官方文檔描述:
These delegate methods are called on the thread that initiated the asynchronous load operation.
NSURLConnection的delegate方法需要在connection發起的線程的runloop中調用。因此,當發起connection的線程exit了,delegate自然不會被調用,請求也就回不來了。因此AF 2.X 加入了NSThread + runLoop去解決這個問題:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
// 創建一個runloop,添加對input source的的監聽
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;
}
而AF 3.0為什么不需要做類似的處理呢?我們看看官方的說明:
Thread Safety
The URL session API itself is fully thread-safe. You can freely create sessions and tasks in any thread context, and when your delegate methods call the provided completion handlers, the work is automatically scheduled on the correct delegate queue