通讀AFN①--從創建manager到數據解析完畢

来源:http://www.cnblogs.com/Mike-zh/archive/2016/01/28/5167017.html
-Advertisement-
Play Games

流程梳理 今天開始會寫幾篇關於AFN源碼解讀的一些Blog,首先要梳理一下AFN的整體結構(主要是討論2.x版本的Session訪問模塊): 我們先看看我們最常用的一段代碼: 在前面關於 AFN URLEncode 的文章說道,AFN將網路訪問分為三個過程化的模塊,下麵我把第一部分再分為兩個步驟:


流程梳理

今天開始會寫幾篇關於AFN源碼解讀的一些Blog,首先要梳理一下AFN的整體結構(主要是討論2.x版本的Session訪問模塊):
我們先看看我們最常用的一段代碼:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    // ... successHandler
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    // ... failureHandler
}];

在前面關於 AFN URLEncode 的文章說道,AFN將網路訪問分為三個過程化的模塊,下麵我把第一部分再分為兩個步驟:

1.訪問前的準備:使用AFURLRequestSerialization類創建一個新的URLRequest對象(用於即將進行的網路訪問),對傳遞過來的URLrequest對象進行三步加工:
①配置預設網路配置,如(allowsCellularAccess,cachePolicy,HTTPShouldHandleCookies,HTTPShouldUsePipelining,networkServiceType,timeoutInterval)
②將request的HTTPHeader賦給新的request
③將parameter字典轉為queryString,拼接在URLRequest的URL後面.如果是POST,PUT,PATCH方法,則放在HTTPBody中,並設置Content-Type頭為表單類型:application/x-www-form-urlencoded

2.用1中所得的mutableRequest對象創建dataTask

3.訪問過程中,將代理職責下放給AFURLSessionManagerTaskDelegate,通過代理方法接收數據。

4.完全接受到數據或失敗之後的處理:失敗回調、成功後解析然後回調。

上面四個步驟都是在[manager GET: parameters: success: failure:]這個方法中完成的,而在進行網路訪問之前的[AFHTTPSessionManager manager]是對網路訪問過程組件的初始化,也就是,在AFHTTPSessionManager+manager方法中,完成了對自己和requestSerializer以及responseSerializer的初始化工作,+manager方法內部的代碼:

self.baseURL = url;

self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];

可以看出requestSerializer和responseSerializer對象都是按照預設的構造方法serializer創建的,同時可以看出responseSerializer預設使用了JSON的解析方式,著也是為什麼當使用AFN進行網路請求時,JSON會自動進行解析的原因。看到這裡我們也瞭解瞭如果想進行修改預設的request和response序列化方式修改,在何時添加這部分代碼。就是在manager的預設設置完成之後,在開始進行網路訪問三步走之前:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
[manager GET:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    // ... successHandler
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    // ... failureHandler
}];

我們能改變的不僅僅是request和reponse按照什麼格式序列化,還可以改變預設的session配置,進行創建Task的session對象在AFN中成為了AFHTTPSessionManager的屬性,如果不使用構造方法- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration傳給它一個值,它會在AFHTTPSessionManager的父類AFURLRequestSerialization中預設配置的,不光如此,而且還配置了AFHTTPSessionManager的很多重要屬性,在AFURLRequestSerialization-initWithBaseURL: sessionConfiguration:中:

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

self.sessionConfiguration = configuration;

self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

self.responseSerializer = [AFJSONResponseSerializer serializer];

self.securityPolicy = [AFSecurityPolicy defaultPolicy];

self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

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

self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
    for (NSURLSessionDataTask *task in dataTasks) {
        [self addDelegateForDataTask:task 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];
    }
}];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:nil];

return self;

這些預設的配置大多是不可以在外部修改,因為大都為readonly屬性,只是在實現文件中給了修改的介面。例如:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 設置最大併發操作數
manager.operationQueue.maxConcurrentOperationCount = 3; // error
[manager GET: parameter: success: failure:];

AFN網路訪問的預設設置大多都是不希望用戶修改的,對外提供的介面也僅僅局限於request和response的序列化方式的修改。
看完了這些,我們來著重看一下網路訪問三步走的過程:

1.訪問前的準備:使用AFURLRequestSerialization類創建一個新的URLRequest對象

這一部分的很多知識點,在這篇文章 iOS. PercentEscape是錯用的URLEncode,看看AFN和Facebook吧中有介紹,這裡說一些補充的內容:
首先是requestSerializer的創建細節:這個雖然不屬於這部分內容(它是在+manager方法中就創建了),但有些問題還需註意:

這個創建過程主要是設置預設編碼為UTF8,對Accept-Language、User-Agent兩個頭的初始化,設置允許queryString放在URL中的HTTP請求方法為@"GET", @"HEAD", @"DELETE"、添加對@[@"allowsCellularAccess", @"cachePolicy", @"HTTPShouldHandleCookies", @"HTTPShouldUsePipelining", @"networkServiceType", @"timeoutInterval"]屬性值(這些key通過一個靜態數組獲得)的觀察者為本身。

需要註意的是請求頭本來是Request的屬性,這裡設置請求頭是用requestSerilizer對象的一個字典屬性mutableHTTPRequestHeaders將它們先存儲起來,以備在修改傳遞過來的request對象過程中使用。

為什麼要KVO以上6個屬性?

字典屬性mutableObservedChangedKeyPaths用來存儲這6個屬性值中非空的值,如果這6個屬性中的任何一個被賦了新值,就會在observeValueForKeyPath:中檢查新值是否為空,如果為空,就從mutableObservedChangedKeyPaths中移出這個對象,表示不再需要考慮這個值對配置的影響。
而這些非空的值會在進行網路訪問前創建新的mutableRequest對象的時候一一賦給它(這些屬性本來就是URLRequest對象的屬性)。

這個過程我們可以換一個思路實現,就是非空給屬性賦值,空時賦給屬性NSNull,在將這些屬性賦給mutableRequest的時候判斷是否為NSNull,如果是,就不賦值了。相比之下AFN的做法對擴展性更好一些。而這種方法的使用在AFN是非常常見的。

下麵我們就看一下mutableRequest創建的細節吧:

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

    // 給mutableRequest賦值剛纔在AFHTTPRequestSerializerObservedKeyPaths存儲的屬性,已經去掉了空值。
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    // 將HTTPRequestHeaders字典屬性中的Header傳給mutableRequest, 將格式化好的queryString傳給mutableRequest
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

剛纔的大費口舌就是剛好是對這段代碼的解釋。
準備好了request,我們就來看一下如何使用request創建dataTask

2.使用準備好的mutableRequest對象創建dataTask

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod: URLString: parameters: failure:方法中的的後半段:

__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { // 下麵會解讀這一句
    if (error) {
        if (failure) {
            failure(dataTask, error);
        }
    } else {
        if (success) {
            success(dataTask, responseObject);
        }
    }
}];

return dataTask;

其中的failure和success實際上是由我們使用者傳遞過來,這段非常簡單的代碼同樣是有點機關的,這其中包含了AFN設計中使用的將代理職責轉移的思想,儘管我們平常也使用過類似的代碼,但還是研讀一下AFN如何實現的吧:

上面的dataTask的創建的核心代碼實現是這樣的:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                            completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    __block NSURLSessionDataTask *dataTask = nil;
    dispatch_sync(url_session_manager_creation_queue(), ^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask completionHandler:completionHandler]; // 下麵有解析

    return dataTask;
}

如上,AFN會選擇在它自定義的串列隊列url_session_manager_creation_queue(這個隊列標記了label:"com.alamofire.networking.session.manager.creation")中採用同步的方式創建dataTask。
在dataTask被創建之後將代理職責下方給了AFURLSessionManagerTaskDelegate對象,我們可以通過查看[self addDelegateForDataTask:dataTask completionHandler:completionHandler];得出:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self; // AFURLSessionManagerTaskDelegate弱引用它的管理者(AFHTTPSessionManager對象)
    delegate.completionHandler = completionHandler; // 將完成的回調(failure和success的處理)傳遞給AFURLSessionManagerTaskDelegate

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];
}

而在的實現中:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [self.lock unlock];
}

這裡AFNHTTPSessionManager將單個dataTask的代理職責下放給了一個AFURLSessionManagerTaskDelegate對象,但是這個對象仍然受manager的控制,manager會用一個可變字典類型的屬性mutableTaskDelegatesKeyedByTaskIdentifier存儲它管理的所有的dataTask和這個dataTask對應的AFURLSessionManagerTaskDelegate對象的關係,而具體的任務下放就是通過這種關係來實現的。

下麵就邊介紹數據請求與接收的過程階段邊解釋如何通過這種關係將代理職責下放。

3.網路訪問過程中

這一過程是由dataTask的resume方法開始的。AFHTTPSessionManager的成員session會使用上面的request進行網路請求,當接收到數據之後進入回調,AFN已將session在AFHTTPSessionManager的父類AFURLSessionManager中預設設置了self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];並且session的代理方法也已在AFURLSessionManager類中實現。而AFN在這個實現的過程中將每次接收到的數據都交給了當前dataTask對應的AFURLSessionManagerTaskDelegate對象處理,在這裡實現了職責下放:
在AFURLSessionManager.m中:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; // 找到dataTask對應的AFURLSessionManagerTaskDelegate對象
    [delegate URLSession:session dataTask:dataTask didReceiveData:data]; // 代理職責下放

    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

// 如何找到dataTask對應的AFURLSessionManagerTaskDelegate對象
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)]; // 根據taskId,之前以key為taskId、value為AFURLSessionManagerTaskDelegate對象的形式存入字典中。
    [self.lock unlock];

    return delegate;
}

而真正處理網路請求的類是AFURLSessionManagerTaskDelegate,它從未被設置為session的delegate,而是在AFHTTPSessionManager(AFURLSessionManager)對session的代理方法的實現中主動調用。

這個數據最後被這樣處理,在AFURLSessionManagerTaskDelegate中

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    [self.mutableData appendData:data];
}

我們可以看到AFURLSessionManagerTaskDelegate類有一個mutableData屬性用來拼接接收的數據。
看一下接收完畢之後是如何處理的,先是在AFURLSessionManager中:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    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);
    }
}

這裡先找到task對應的AFURLSessionManagerTaskDelegate對象,同樣是通過dataTask的Id,然後將處理任務交給這個delegate對象,等它處理之後,sessionManager會將這個delegate對象從字典中移除:

- (void)removeDelegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    [self.lock lock];
    [delegate cleanUpProgressForTask:task];
    [self removeNotificationObserverForTask:task];
    [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
}

這樣manager管理的session進行的一次dataTask就完畢了。

再看一下在AFURLSessionManagerTaskDelegate中,如何具體處理的

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }

    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

        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(), ^{
            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(), ^{
                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
}

取出SessionManager,和sessionManager的responseSerializer屬性,創建userInfo字典,存放數據解析的組件對象和返回的數據等

如果有錯誤:
1.userInfo存入error,key為完成錯誤的標記,
2.創建隊列任務:在主隊列中完成回調(由最開始傳入的success和failure處理)、然後向主線程發送附帶userInfo的任務完成的通知,
3.將2創建的任務放在靜態的隊列組url_session_manager_completion_group()中執行。

沒有錯誤:
在非同步的靜態隊列url_session_manager_processing_queue(label是"com.alamofire.networking.session.manager.processing")中處理:
1.用manager的responseSerializer屬性進行數據解析,將data解析為responseObject
1.1.解析正確,將responseObject存入userInfo中,
1.2.解析失敗,將錯誤信息serializationError存入userInfo,
2.創建隊列任務:在主隊列中完成回調(由最開始傳入的success和failure處理)、然後向主線程發送附帶userInfo的任務完成的通知,
3.將2創建的任務放在靜態的隊列組url_session_manager_completion_group()中執行。

要說明的一點是:AFN只負責發送通知,而沒有對通知進行接收的處理,這部分需要使用者自己完成。
現在就只剩下數據解析的過程了還沒有介紹了。

4.數據解析

這裡主要體現的是面向對象多態的特性。
在無論我們使用AFHTTPSessionManager對象或是使用AFURLSessionManager對象創建的dataTask在數據解析階段,都會調用上面剛剛分析完的代碼中的responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];這一句進行數據解析,而在HTTPSessionManager的manager方法中預設為我們創建了JSON類型的解析器self.responseSerializer = [AFJSONResponseSerializer serializer];,這樣在執行過程中,就會動態地調用AFJSONResponseSerializer的-responseObjectForResponse: data: error:方法,它的實現是這樣的:

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data 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;
}

這是一個非常簡單的演算法,
1.先調用了從父類(AFHTTPResponseSerializer)集成而來的數據驗證方法,如果驗證失敗了,並且確認錯誤是由AFN解析引起的,返回nil,
2.檢驗data是否為空或者一個空格這樣的無效數據,失敗返回nil,否則將data解析為JSONObject
3.如果removesKeysWithNullValues屬性設置為YES,那麼要去掉2中的JSONObject中的value等於[NSNull null]的元素。

AFJSONResponseSerializer類是AFHTTPResponseSerializer的子類,一些初始化的設置,還有驗證數據的方法都是在AFJSONResponseSerializer中完成的。

看一下AFJSONResponseSerializer類:

- (instancetype)init {
    // ...
    self.stringEncoding = NSUTF8StringEncoding;

    self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; // 只接受statusCode為2xx
    self.acceptableContentTypes = nil; // 接收的Content-Type,需要子類的init中重寫

    return self;
}

- (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]]) {
            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;
}

init不再多說,主要是驗證方法- (BOOL)validateResponse: data: error:,在這個方法內部完成了這些工作:
1.設置驗證通過responseIsValid的預設值YES,錯誤validationError為nil
2.驗證
 2.1對response的MIME類型驗證:如果acceptableContentTypes屬性中不包含response的MIME類型,則認為驗證失敗,responseIsValid設為NO,本地化錯誤描述,並將描述、response的URL、response對象存入userInfo字典,用這個userInfo字典創建Domain為AFURLResponseSerializationErrorDomain的NSError對象
 2.2對response.statusCode驗證:如果acceptableStatusCodes屬性中不包含response.statusCode,則認為失敗,處理同2.1,
3.將錯誤賦給參數error,返回responseIsValid。


對於其他類型的解析與JSON類似,這裡列舉一下經過解析後的的id responseObject對應的類型:

manager的responseSerializer屬性類型 解析後的responseObject類型
AFHTTPResponseSerializer NSData
AFJSONResponseSerializer JSONObject(NSDictionary或NSArray)
AFXMLParserResponseSerializer NSXMLParser
AFXMLDocumentResponseSerializer NSXMLDocument
AFPropertyListResponseSerializer propertyList(NSDictionary或NSArray)
AFImageResponseSerializer iOS、TV、Watch:UIImage    Mac:NSImage
AFCompoundResponseSerializer 用responseSerializers數組中對象依次解析,
第一個失敗,則用第二個解析,依次類推,返回第一個成功的結果

當獲取responseObject對象後,直接按類型使用即可,例如如果設置了manager.responseSerializer = [AFXMLParserResponseSerializer serializer],就要這樣解析:

success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSXMLParser *saxParser = (NSXMLParser *)responseObject;
    saxParser.delegate = self;
    [saxParser parse];
}

不過若要使用相同的manager對象進行下一次網路訪問,如果不知道response的Content-Type,就要將manager的responseSerializer複原,重新設置為:

manager.responseSerializer = [AFJSONRequestSerializer serializer]; // 如果manager為AFHTTPSessionManager
manager.responseSerializer = [AFHTTPRequestSerializer serializer]; // 如果manager為AFURLSessionManager

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • javascript截取字元串代碼實例:截取字元串是常見的操作,例如新聞列表對新聞標題長度的控制就是如此,下麵分享一段這樣的代碼實例,也不多做介紹了,因為代碼很簡單,很多朋友需要的僅僅是一個例子而已,因為只要看到例子就知道該怎麼用了。代碼如下: var str="螞蟻部落歡迎您,只有努力才會有美好的
  • css如何實現讓文字沉到元素的底部:在實際應用中可能有這樣的需求,那就是將指定的文本沉降到元素的底部。下麵就通過代碼實例介紹以下如何實現此效果。代碼如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author
  • 1.介面部分:對外聲明類的行為和特征(類的定義分為:介面部分和實現部分) ① @interface 介面關鍵字:用於表示這是一個類的介面部分 介面部分功能:是定義類的靜態特征和聲明動態行為 @end 作為結束標誌 對外介面:通過介面就可以在不知道實現的情況下,瞭解這個類有什麼 Person:類名,每
  • 在web頁面中,有a標簽的超鏈接實現跳轉,同樣在Android當中,用TextView控制項來顯示文字,實現它的事件來跳轉。 核心代碼如下: //以下代碼寫在onCreate()方法當中 textView1=(TextView)findViewById(R.id.sound_help); String
  • Remote Displayer for Android V1.0.
  • 代碼: #import "RootViewController.h" //為判斷手機的型號 -(NSString*)deviceString添加頭文件 #import "sys/utsname.h" @interface RootViewController () @end @implementat
  • 一、問題描述 使用百度地圖實現如圖所示應用,首先自動定位當前我起始位置(小圓點位置),並跟隨移動不斷自動定位我的當前位置 百度Api不同版本使用會有些差異,本例中加入lib如下: 二、編寫MyApplication類 public class MyApplication extends Applic
  • 介紹 最近要使用播放器做一個簡單的視頻播放功能,開始學習VideoView,在橫豎屏切換的時候碰到了點麻煩,不過在查閱資料後總算是解決了。在寫VideoView播放視頻時候定義控制的代碼全寫在Actvity里了,寫完一看我靠代碼好亂,於是就寫了個自定義的播放器控制項,支持指定大小,可以橫豎屏切換,手動
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...