AFNetworking3.2.1源碼解讀

AFNetworking3.2.1源碼解讀

下面是AFNetworking的源碼結(jié)構(gòu)圖,主要分為:AFURLSessionManager、AFNetworkReachabilityManager、AFSecurityPolicy、Serialization和UIKit等5部分。

AFNetworking的源碼結(jié)構(gòu)圖

AFNetworking

詳細(xì)架構(gòu)

下面我們就看一下AFN的詳細(xì)架構(gòu)

NSURLSession

  • AFURLSessionManager
  • AFHTTPSessionManager

Serialization

  • <AFURLRequestSerialization>
  1. AFHTTPRequestSerializer
  2. AFJSONRequestSerializer
  3. AFPropertyListRequestSerializer
  • <AFURLResponseSerialization>
  1. AFHTTPResponseSerializer
  2. AFJSONResponseSerializer
  3. AFXMLParserResponseSerializer
  4. AFXMLDocumentResponseSerializer (macOS)
  5. AFPropertyListResponseSerializer
  6. AFImageResponseSerializer
  7. AFCompoundResponseSerializer

Additional Functionality

  • AFSecurityPolicy
  • AFNetworkReachabilityManager

NSURLSessionDataTask實(shí)例化

先看一個(gè)項(xiàng)目中進(jìn)行的GET請(qǐng)求,直接接入的就是AFN中的下面這個(gè)方法

**
 不需要進(jìn)度回調(diào)
 Creates and runs an `NSURLSessionDataTask` with a `GET` request.

 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded according to the client request serializer.
 @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
 @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

 @see -dataTaskWithRequest:completionHandler:
 */
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(nullable id)parameters
                      success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                      failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;


/**
 需要進(jìn)度的回調(diào)
 Creates and runs an `NSURLSessionDataTask` with a `GET` request.

 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded according to the client request serializer.
 @param downloadProgress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue.
 @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
 @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

 @see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
 */
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
                            parameters:(nullable id)parameters
                              progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
                               success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                               failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

Get請(qǐng)求實(shí)現(xiàn)

  1. 接口的調(diào)用
// 不需要進(jìn)度回調(diào)
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                      failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{

    return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
}
// 需要進(jìn)度回調(diào)
- (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;
}

這里面五個(gè)參數(shù),很好理解,請(qǐng)求的URL、參數(shù)、進(jìn)度block、成功block和失敗block。

下面我們看NSURLSessionDataTask這個(gè)類

/*
 * An NSURLSessionDataTask does not provide any additional
 * functionality over an NSURLSessionTask and its presence is merely
 * to provide lexical differentiation from download and upload tasks.
 */
@interface NSURLSessionDataTask : NSURLSessionTask
@end

NSURLSessionDataTask不提供NSURLSessionTask的任何附加功能,它的存在僅僅是為了提供下載和上載任務(wù)的詞匯區(qū)分。

  1. NSURLSessionDataTask的實(shí)例化
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }

        return nil;
    }

    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

這里沒(méi)用到上傳,所以u(píng)ploadProgress參數(shù)為nil,這種調(diào)用方式大家是不是很熟悉,感覺(jué)很好,對(duì)了,SDWebImage下載圖像的接口就是這么調(diào)用的,最后走的都是同一個(gè)方法,只是個(gè)別參數(shù)為nil或0,最后在這個(gè)參數(shù)最全的方法里面做一些差別化的處理。大神都是這么寫(xiě)代碼,不僅代碼邏輯清晰,而且調(diào)用和查看代碼也很方便。

這里做了兩個(gè)方面的工作:

  • 實(shí)例化NSMutableURLRequest請(qǐng)求對(duì)象。
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
  • 實(shí)例化NSURLSessionDataTask對(duì)象,并調(diào)用下面方法返回該對(duì)象。
- (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;
}

//為指定的任務(wù)添加代理
//首先實(shí)例化AFURLSessionManagerTaskDelegate,并對(duì)改類內(nèi)部的屬性進(jìn)行賦值,并調(diào)用下面的方法為task設(shè)置delegate。
- (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;
}

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);
    //加鎖 需要保護(hù)的內(nèi)容放在中間,讓數(shù)據(jù)更安全
    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [self addNotificationObserverForTask:task]; //通知 監(jiān)聽(tīng)任務(wù)的開(kāi)始和暫停
    [self.lock unlock];
}

任務(wù)進(jìn)度設(shè)置和通知監(jiān)聽(tīng)

  1. 上傳進(jìn)度
- (instancetype)initWithTask:(NSURLSessionTask *)task {
    self = [super init];
    if (!self) {
        return nil;
    }
    
    _mutableData = [NSMutableData data];
    _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];
    }
    return self;
}
  • 取消
progress.cancellable = YES;
progress.cancellationHandler = ^{
    [weakTask cancel];
};

首先要設(shè)置的就是可取消cancallable這個(gè)屬性,需要設(shè)置為YES。

@property (getter=isCancellable) BOOL cancellable;

所做的工作是否可以分別取消或暫停。 默認(rèn)情況下,NSProgresses是可取消的,但不可pausable。 對(duì)于這些屬性,NSProgress默認(rèn)為符合KVO標(biāo)準(zhǔn),并且通知始終在更新屬性的線程上發(fā)送。 這些屬性用于傳遞是否應(yīng)該在進(jìn)度報(bào)告用戶界面中顯示取消和暫停的控件。 NSProgress本身不會(huì)對(duì)這些屬性做任何事情,除了幫助將進(jìn)度記錄的值傳遞給進(jìn)度觀察員。 在NSProgress的生命周期中,這些屬性的值實(shí)際上以任何方式改變都是有效的。 當(dāng)然,如果一個(gè)NSProgress可以被取消,你應(yīng)該通過(guò)設(shè)置一個(gè)取消處理程序或者讓你的代碼輪詢調(diào)用-isCancelled的結(jié)果來(lái)實(shí)現(xiàn)可取消性。 同樣適用于pausability。

- (void)cancel;

cancel立即返回,但將任務(wù)標(biāo)記為被取消。 該任務(wù)將發(fā)信號(hào)-URLSession:task:didCompleteWithError:錯(cuò)誤值為{NSURLErrorDomain,NSURLErrorCancelled}。 在某些情況下,任務(wù)可能在確認(rèn)取消之前發(fā)出其他工作的信號(hào)。- cancel可能被發(fā)送到已被暫停的任務(wù)。

  • 暫停
progress.pausable = YES;
progress.pausingHandler = ^{
    [weakTask suspend];
};

暫停任務(wù)將阻止NSURLSession繼續(xù)加載數(shù)據(jù)。 可能仍然存在代表此任務(wù)的代理在調(diào)用(例如,報(bào)告掛起時(shí)收到的數(shù)據(jù)),但不會(huì)有代表任務(wù)進(jìn)行進(jìn)一步的傳輸直到發(fā)送- resume。 與任務(wù)關(guān)聯(lián)的超時(shí)定時(shí)器將在任務(wù)暫停時(shí)被禁用。 - ususpend和- resume是可嵌套的。

  • 開(kāi)始
#if AF_CAN_USE_AT_AVAILABLE
    if (@available(iOS 9, macOS 10.11, *))
#else
    if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
    {
        progress.resumingHandler = ^{
            [weakTask resume];
        };
    }

resumingHandler,開(kāi)始進(jìn)行任務(wù)的處理

@property (nullable, copy) void (^resumingHandler)(void) NS_AVAILABLE(10_11, 9_0);

調(diào)用resume時(shí)要調(diào)用的塊。 即使該方法在接收方的super類上調(diào)用,或者由于發(fā)布接收方或接收方的super類而導(dǎo)致的另一個(gè)進(jìn)程中的NSProgress實(shí)例,也會(huì)調(diào)用該block。 您的塊不會(huì)在任何特定隊(duì)列上調(diào)用。 如果它必須在特定的隊(duì)列上工作,那么它應(yīng)該在該隊(duì)列上安排該工作。

  1. 下載進(jìn)度

同上

  1. 上傳下載進(jìn)度增加KVO觀察
[progress addObserver:self
           forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
              options:NSKeyValueObservingOptionNew
              context:NULL];

KVO的監(jiān)聽(tīng)部分如下

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
   if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

AFURLSessionManager為任務(wù)添加通知監(jiān)聽(tīng)

<!--添加通知監(jiān)聽(tīng)-->
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
  1. 開(kāi)始
- (NSString *)taskDescriptionForSessionTasks {
    return [NSString stringWithFormat:@"%p", self];
}

- (void)taskDidResume:(NSNotification *)notification {
    NSURLSessionTask *task = notification.object;
    if ([task respondsToSelector:@selector(taskDescription)]) {
        if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
            });
        }
    }
}

一起來(lái)看一下這里的邏輯,首先就是取出任務(wù)notification.object,然后判斷[task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks],這里self.taskDescriptionForSessionTasks的意思就是當(dāng)前實(shí)例化對(duì)象的地址。判斷如果是YES,那么就在主線程發(fā)送通知。

  1. 暫停
- (void)taskDidSuspend:(NSNotification *)notification {
    NSURLSessionTask *task = notification.object;
    if ([task respondsToSelector:@selector(taskDescription)]) {
        if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
            });
        }
    }
}

暫停的邏輯參考開(kāi)始,不同的地方就在于發(fā)送通知的name不一樣而已。

代理轉(zhuǎn)發(fā)思想

重寫(xiě)respondsToSelector方法

- (BOOL)respondsToSelector:(SEL)selector {
    if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
        return self.taskWillPerformHTTPRedirection != nil;
    } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
        return self.dataTaskDidReceiveResponse != nil;
    } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
        return self.dataTaskWillCacheResponse != nil;
    }
#if !TARGET_OS_OSX
    else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
        return self.didFinishEventsForBackgroundURLSession != nil;
    }
#endif

    return [[self class] instancesRespondToSelector:selector];
}

重寫(xiě)respondsToSelector的作用就是判斷NSURLSession那幾個(gè)代理是否能響應(yīng),如果響應(yīng)了就去調(diào)用。復(fù)寫(xiě)了selector的方法,這幾個(gè)方法是在本類有實(shí)現(xiàn)的,但是如果外面的Block沒(méi)賦值的話,則返回NO,相當(dāng)于沒(méi)有實(shí)現(xiàn)。

這樣如果沒(méi)實(shí)現(xiàn)這些我們自定義的Block也不會(huì)去回調(diào)這些代理。因?yàn)楸旧砟承┐恚粓?zhí)行了這些自定義的Block,如果Block都沒(méi)有賦值,那我們調(diào)用代理也沒(méi)有任何意義。

block的屬性和值的設(shè)置

定義的block的屬性

@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession AF_API_UNAVAILABLE(macos);
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);

typedef NSURLRequest * (^AFURLSessionTaskWillPerformHTTPRedirectionBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request);
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionTaskDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);
typedef void (^AFURLSessionDidFinishEventsForBackgroundURLSessionBlock)(NSURLSession *session);

typedef NSInputStream * (^AFURLSessionTaskNeedNewBodyStreamBlock)(NSURLSession *session, NSURLSessionTask *task);
typedef void (^AFURLSessionTaskDidSendBodyDataBlock)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend);
typedef void (^AFURLSessionTaskDidCompleteBlock)(NSURLSession *session, NSURLSessionTask *task, NSError *error);

typedef NSURLSessionResponseDisposition (^AFURLSessionDataTaskDidReceiveResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response);
typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask);
typedef void (^AFURLSessionDataTaskDidReceiveDataBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data);
typedef NSCachedURLResponse * (^AFURLSessionDataTaskWillCacheResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse);

typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes);
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);

typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error);

下面看一下這些屬性的值的設(shè)定。在.h文件中有接口,并在.m中是實(shí)現(xiàn),這里就舉一個(gè)例子,如下:

<!--.h-->
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;

<!--.m-->
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
    self.sessionDidBecomeInvalid = block;
}

這么做的目的是為了我們這些用戶使用起來(lái)方便,調(diào)用set方法去設(shè)置這些Block,能很清晰的看到Block的各個(gè)參數(shù)與返回值。

代理轉(zhuǎn)發(fā)思想

AFURLSessionManager中是有關(guān)NSURLSession的代理,主要包含下面的幾個(gè)代理的實(shí)現(xiàn)。

AFURLSessionManagerTaskDelegate2

上面的幾個(gè)代理是什么關(guān)系,相信看過(guò)我寫(xiě)的SDWebImage的源碼分析的應(yīng)該很清楚,他們是繼承的關(guān)系,就不多說(shuō)了。而AFURLSessionManagerTaskDelegate實(shí)現(xiàn)了三個(gè)代理方法。

AFURLSessionManagerTaskDelegate

AFUrlSessionManager對(duì)這一大堆代理做了一些公共的處理,而轉(zhuǎn)發(fā)到AF自定義代理的6條,則負(fù)責(zé)把每個(gè)task對(duì)應(yīng)的數(shù)據(jù)回調(diào)出去。

那么是如何轉(zhuǎn)發(fā)過(guò)去的呢,簡(jiǎn)單的調(diào)用就是這樣的。

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

- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];

    return delegate;
}

通過(guò)[delegate URLSession:session task:task didCompleteWithError:error];就轉(zhuǎn)發(fā)了過(guò)去。這里首先就是從字典中找到該任務(wù)對(duì)應(yīng)的代理AFURLSessionManagerTaskDelegate對(duì)象,然后就是轉(zhuǎn)發(fā)到自定義代理中。

大家如果讀過(guò)SDWebImage的源碼的話,就應(yīng)該明白,那里也用到了代理的轉(zhuǎn)發(fā),可見(jiàn),這種轉(zhuǎn)發(fā)思想有它的優(yōu)點(diǎn),值得我們?nèi)W(xué)習(xí)。

NSURLSessionDelegate詳細(xì)解析

NSURLSession的代理在類AFURLSessionManager中實(shí)現(xiàn),轉(zhuǎn)發(fā)到AFURLSessionManagerTaskDelegate自定義的代理中。下面開(kāi)始會(huì)介紹這個(gè)NSURLSession的代理的用法。

1. URLSession:didBecomeInvalidWithError:

先看一下系統(tǒng)的API。

/* The last message a session receives.  A session will only become
 * invalid because of a systemic error or when it has been
 * explicitly invalidated, in which case the error parameter will be nil.
 */
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;

下面是代理方法的實(shí)現(xiàn)

@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);

- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

如果你使用finishTasksAndInvalidate函數(shù)使該session失效,
那么session首先會(huì)先完成最后一個(gè)task,然后再調(diào)用URLSession:didBecomeInvalidWithError:代理方法,
如果你調(diào)用invalidateAndCancel方法來(lái)使session失效,那么該session會(huì)立即調(diào)用上面的代理方法。

這個(gè)就是該代理方法調(diào)用的時(shí)機(jī)。

這個(gè)代理方法做了兩個(gè)事情:

  • 調(diào)用了自定義的block - sessionDidBecomeInvalid
  • 發(fā)送了一個(gè)通知,但是沒(méi)有對(duì)象去監(jiān)聽(tīng),可能是作者故意留的接口吧,這個(gè)通知目前是沒(méi)有用到。

2. URLSession:task:didReceiveChallenge:completionHandler:

我們先看一下系統(tǒng)定義的API

/* The task has received a request specific authentication challenge.
 * If this delegate is not implemented, the session specific authentication challenge
 * will *NOT* be called and the behavior will be the same as using the default handling
 * disposition. 
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                            didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
                              completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

下面看一下在該類中的實(shí)現(xiàn)。

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

這里其實(shí)就是HTTPS認(rèn)證,服務(wù)器向客戶端進(jìn)行質(zhì)詢和挑戰(zhàn)。這個(gè)類似的其實(shí)在SDWebImage中講過(guò),這里也簡(jiǎn)單的看一下。

web服務(wù)器接收到客戶端請(qǐng)求時(shí),有時(shí)候需要先驗(yàn)證客戶端是否為正常用戶,再?zèng)Q定是夠返回真實(shí)數(shù)據(jù)。這種情況稱之為服務(wù)端要求客戶端接收挑戰(zhàn)(NSURLAuthenticationChallenge *challenge)。接收到挑戰(zhàn)后,客戶端要根據(jù)服務(wù)端傳來(lái)的challenge來(lái)生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定應(yīng)對(duì)這個(gè)挑戰(zhàn)的方法,而credential是客戶端生成的挑戰(zhàn)證書(shū),注意只有challenge中認(rèn)證方法為NSURLAuthenticationMethodServerTrust的時(shí)候,才需要生成挑戰(zhàn)證書(shū))。最后調(diào)用completionHandler回應(yīng)服務(wù)器端的挑戰(zhàn)。

當(dāng)某個(gè)session使用SSL/TLS協(xié)議,第一次和服務(wù)器端建立連接的時(shí)候,服務(wù)器會(huì)發(fā)送給iOS客戶端一個(gè)證書(shū),此方法允許你的app驗(yàn)證服務(wù)期端的證書(shū)鏈(certificate keychain)注:如果你沒(méi)有實(shí)現(xiàn)該方法,該session會(huì)調(diào)用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler:。

3. URLSessionDidFinishEventsForBackgroundURLSession:

先看一下系統(tǒng)的API。

/* If an application has received an
 * -application:handleEventsForBackgroundURLSession:completionHandler:
 * message, the session delegate will receive this message to indicate
 * that all messages previously enqueued for this session have been
 * delivered.  At this time it is safe to invoke the previously stored
 * completion handler, or to begin any internal updates that will
 * result in invoking the completion handler.
 */
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session API_AVAILABLE(ios(7.0), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);

下面看一下AFN中的實(shí)現(xiàn)

@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession AF_API_UNAVAILABLE(macos);
typedef void (^AFURLSessionDidFinishEventsForBackgroundURLSessionBlock)(NSURLSession *session);

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.didFinishEventsForBackgroundURLSession) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.didFinishEventsForBackgroundURLSession(session);
        });
    }
}
  • 當(dāng)session中所有已經(jīng)入隊(duì)的消息被發(fā)送出去后,會(huì)調(diào)用該代理方法。
  • 在iOS中,當(dāng)一個(gè)后臺(tái)傳輸任務(wù)完成或者后臺(tái)傳輸時(shí)需要證書(shū),而此時(shí)你的app正在后臺(tái)掛起,那么你的app在后臺(tái)會(huì)自動(dòng)重新啟動(dòng)運(yùn)行,并且這個(gè)app的UIApplicationDelegate會(huì)發(fā)送一個(gè)application:handleEventsForBackgroundURLSession:completionHandler:消息。該消息包含了對(duì)應(yīng)后臺(tái)的session的identifier,而且這個(gè)消息會(huì)導(dǎo)致你的app啟動(dòng)。你的app隨后應(yīng)該先存儲(chǔ)completion handler,然后再使用相同的identifier創(chuàng)建一個(gè)background configuration,并根據(jù)這個(gè)background configuration創(chuàng)建一個(gè)新的session。這個(gè)新創(chuàng)建的session會(huì)自動(dòng)與后臺(tái)任務(wù)重新關(guān)聯(lián)在一起。
  • 當(dāng)你的app獲取了一個(gè)URLSessionDidFinishEventsForBackgroundURLSession:消息,這就意味著之前這個(gè)session中已經(jīng)入隊(duì)的所有消息都轉(zhuǎn)發(fā)出去了,這時(shí)候再調(diào)用先前存取的completion handler是安全的,或者因?yàn)閮?nèi)部更新而導(dǎo)致調(diào)用completion handler也是安全的。

NSURLSessionTaskDelegate

1. URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:

首先看一下系統(tǒng)的API接口

/* An HTTP request is attempting to perform a redirection to a different
 * URL. You must invoke the completion routine to allow the
 * redirection, allow the redirection with a modified request, or
 * pass nil to the completionHandler to cause the body of the redirection 
 * response to be delivered as the payload of this request. The default
 * is to follow redirections. 
 *
 * For tasks in background sessions, redirections will always be followed and this method will not be called.
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                     willPerformHTTPRedirection:(NSHTTPURLResponse *)response
                                     newRequest:(NSURLRequest *)request
                              completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

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

這里,主要做了下面幾個(gè)邏輯:

如果有對(duì)應(yīng)的block taskWillPerformHTTPRedirection,那么就調(diào)用self.taskWillPerformHTTPRedirection(session, task, response, request),返回一個(gè)新的request。
接著就是利用生成的request重新請(qǐng)求

if (completionHandler) {
    completionHandler(redirectRequest);
}

這個(gè)方法是在服務(wù)器去重定向的時(shí)候,才會(huì)被調(diào)用。
此方法只會(huì)在default session或者ephemeral session中調(diào)用,而在background session中,session task會(huì)自動(dòng)重定向。
補(bǔ)充一點(diǎn),初始化NSURLSession對(duì)象的時(shí)候需要使用NSURLSessionConfiguration。有三個(gè)類工廠方法:

  • + defaultSessionConfiguration 返回一個(gè)標(biāo)準(zhǔn)的 configuration,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享NSURLCredentialStorage。
  • + ephemeralSessionConfiguration 返回一個(gè)預(yù)設(shè)配置,這個(gè)配置中不會(huì)對(duì)緩存,Cookie 和證書(shū)進(jìn)行持久性的存儲(chǔ)。這對(duì)于實(shí)現(xiàn)像秘密瀏覽這種功能來(lái)說(shuō)是很理想的。
  • + backgroundSessionConfiguration:(NSString *)identifier 的獨(dú)特之處在于,它會(huì)創(chuàng)建一個(gè)后臺(tái) session。后臺(tái) session 不同于常規(guī)的,普通的 session,它甚至可以在應(yīng)用程序掛起,退出或者崩潰的情況下運(yùn)行上傳和下載任務(wù)。初始化時(shí)指定的標(biāo)識(shí)符,被用于向任何可能在進(jìn)程外恢復(fù)后臺(tái)傳輸?shù)氖刈o(hù)進(jìn)程(daemon)提供上下文。

2. URLSession:task:didReceiveChallenge:completionHandler

首先看一下系統(tǒng)的API接口

/* The task has received a request specific authentication challenge.
 * If this delegate is not implemented, the session specific authentication challenge
 * will *NOT* be called and the behavior will be the same as using the default handling
 * disposition. 
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                            didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
                              completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

這里我們需要知道:

  • 這個(gè)是non-session-level級(jí)別的認(rèn)證
  • 多了一個(gè)參數(shù)task,然后調(diào)用我們自定義的Block會(huì)多回傳這個(gè)task作為參數(shù),這樣我們就可以根據(jù)每個(gè)task去自定義我們需要的https認(rèn)證方式。

3. URLSession:task:needNewBodyStream

首先看一下系統(tǒng)的API接口

/* Sent if a task requires a new, unopened body stream.  This may be
 * necessary when authentication has failed for any request that
 * involves a body stream. 
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                              needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{
    NSInputStream *inputStream = nil;

    if (self.taskNeedNewBodyStream) {
        inputStream = self.taskNeedNewBodyStream(session, task);
    } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
        inputStream = [task.originalRequest.HTTPBodyStream copy];
    }

    if (completionHandler) {
        completionHandler(inputStream);
    }
}

該代理方法會(huì)在下面兩種情況被調(diào)用

  • 如果task是由uploadTaskWithStreamedRequest:創(chuàng)建的,那么提供初始的request body stream時(shí)候會(huì)調(diào)用該代理方法。
  • 因?yàn)檎J(rèn)證挑戰(zhàn)或者其他可恢復(fù)的服務(wù)器錯(cuò)誤,而導(dǎo)致需要客戶端重新發(fā)送一個(gè)含有body stream的request,這時(shí)候會(huì)調(diào)用該代理。

4. URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend

首先看一下系統(tǒng)的API接口

/* Sent periodically to notify the delegate of upload progress.  This
 * information is also available as properties of the task.
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                                didSendBodyData:(int64_t)bytesSent
                                 totalBytesSent:(int64_t)totalBytesSent
                       totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{

    int64_t totalUnitCount = totalBytesExpectedToSend;
    if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
        NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
        if(contentLength) {
            totalUnitCount = (int64_t) [contentLength longLongValue];
        }
    }
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    
    if (delegate) {
        [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
    }

    if (self.taskDidSendBodyData) {
        self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
    }
}

就是每次發(fā)送數(shù)據(jù)給服務(wù)器,會(huì)回調(diào)這個(gè)方法,通知已經(jīng)發(fā)送了多少。

5. URLSession:task:didCompleteWithError

首先看一下系統(tǒng)的API接口

/* Sent as the last message related to a specific task.  Error may be
 * nil, which implies that no error occurred and this task is complete. 
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                           didCompleteWithError:(nullable NSError *)error;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

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

這個(gè)代理就是task完成了的回調(diào),這里涉及到task和代理的綁定與接觸綁定,代碼不難,就不多說(shuō)了。

NSURLSessionDataDelegate

1. URLSession:dataTask:didReceiveResponse:completionHandler

首先看一下系統(tǒng)的API接口

/* The task has received a response and no further messages will be
 * received until the completion block is called. The disposition
 * allows you to cancel a request or to turn a data task into a
 * download task. This delegate message is optional - if you do not
 * implement it, you can get the response as a property of the task.
 *
 * This method will not be called for background upload tasks (which cannot be converted to download tasks).
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                 didReceiveResponse:(NSURLResponse *)response
                                  completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;

    if (self.dataTaskDidReceiveResponse) {
        disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
    }

    if (completionHandler) {
        completionHandler(disposition);
    }
}

這里,NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;表示默認(rèn)為繼續(xù)進(jìn)行。

  • completionHandler這個(gè)block,通過(guò)傳入一個(gè)類型為NSURLSessionResponseDisposition的變量來(lái)決定該傳輸任務(wù)接下來(lái)該做什么:
  1. NSURLSessionResponseAllow 該task正常進(jìn)行
  2. NSURLSessionResponseCancel 該task會(huì)被取消
  3. NSURLSessionResponseBecomeDownload 會(huì)調(diào)用URLSession:dataTask:didBecomeDownloadTask:方法來(lái)新建一個(gè)download task以代替當(dāng)前的data task
  4. NSURLSessionResponseBecomeStream 轉(zhuǎn)成一個(gè)StreamTask
  • 當(dāng)你把添加content-type的類型為multipart/x-mixed-replace那么服務(wù)器的數(shù)據(jù)會(huì)分片的傳回來(lái)。然后這個(gè)方法是每次接受到對(duì)應(yīng)片響應(yīng)的時(shí)候會(huì)調(diào)被調(diào)用。你應(yīng)該在這個(gè)函數(shù)中合理地處理先前的數(shù)據(jù),否則會(huì)被新數(shù)據(jù)覆蓋。

2. URLSession:dataTask:didBecomeDownloadTask

首先看一下系統(tǒng)的API接口

/*
 * Notification that a data task has become a bidirectional stream
 * task.  No future messages will be sent to the data task.  The newly
 * created streamTask will carry the original request and response as
 * properties.
 *
 * For requests that were pipelined, the stream object will only allow
 * reading, and the object will immediately issue a
 * -URLSession:writeClosedForStream:.  Pipelining can be disabled for
 * all requests in a session, or by the NSURLRequest
 * HTTPShouldUsePipelining property.
 *
 * The underlying connection is no longer considered part of the HTTP
 * connection cache and won't count against the total number of
 * connections per host.
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    if (delegate) {
        [self removeDelegateForTask:dataTask];
        [self setDelegate:delegate forTask:downloadTask];
    }

    if (self.dataTaskDidBecomeDownloadTask) {
        self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
    }
}

當(dāng)代理1方法中的disposition = NSURLSessionResponseBecomeDownload的時(shí)候,就會(huì)調(diào)用這個(gè)方法。

這個(gè)代理方法是被上面的代理方法觸發(fā)的,作用就是新建一個(gè)downloadTask,替換掉當(dāng)前的dataTask。所以我們?cè)谶@里做了AF自定義代理的重新綁定操作[self setDelegate:delegate forTask:downloadTask];。

3. URLSession:dataTask:didReceiveData

首先看一下系統(tǒng)的API接口

/* Sent when data is available for the delegate to consume.  It is
 * assumed that the delegate will retain and not copy the data.  As
 * the data may be discontiguous, you should use 
 * [NSData enumerateByteRangesUsingBlock:] to access it.
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                     didReceiveData:(NSData *)data;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];

    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}
  • 獲取到數(shù)據(jù)就會(huì)調(diào)用,會(huì)被反復(fù)調(diào)用,請(qǐng)求到的數(shù)據(jù)就在這被拼裝完整。
  • 這個(gè)方法和上面didCompleteWithError算是NSURLSession的代理中最重要的兩個(gè)方法。
  • 我們轉(zhuǎn)發(fā)了這個(gè)方法到AF的代理中去,所以數(shù)據(jù)的拼接都是在AF的代理中進(jìn)行的。這也是情理中的,畢竟每個(gè)響應(yīng)數(shù)據(jù)都是對(duì)應(yīng)各個(gè)task,各個(gè)AF代理的。在AFURLSessionManager都只是做一些公共的處理。

4. URLSession:dataTask:willCacheResponse:completionHandler

首先看一下系統(tǒng)的API接口

/* Invoke the completion routine with a valid NSCachedURLResponse to
 * allow the resulting data to be cached, or pass nil to prevent
 * caching. Note that there is no guarantee that caching will be
 * attempted for a given resource, and you should not rely on this
 * message to receive the resource data.
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                  willCacheResponse:(NSCachedURLResponse *)proposedResponse 
                                  completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler;

下面看一下AFN中該代理方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
    NSCachedURLResponse *cachedResponse = proposedResponse;

    if (self.dataTaskWillCacheResponse) {
        cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
    }

    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}
  • 該方法的作用就是詢問(wèn)data task或上傳任務(wù)(upload task)是否緩存response。
  • 當(dāng)task接收到所有期望的數(shù)據(jù)后,session會(huì)調(diào)用此代理方法。
  • 當(dāng)task接收到所有期望的數(shù)據(jù)后,session會(huì)調(diào)用此代理方法。如果你沒(méi)有實(shí)現(xiàn)該方法,那么就會(huì)使用創(chuàng)建session時(shí)使用的configuration對(duì)象決定緩存策略。這個(gè)代理方法最初的目的是為了阻止緩存特定的URLs或者修改NSCacheURLResponse對(duì)象相關(guān)的userInfo字典。
  • 該方法只會(huì)當(dāng)request決定緩存response時(shí)候調(diào)用。作為準(zhǔn)則,responses只會(huì)當(dāng)以下條件都成立的時(shí)候返回緩存:
  1. 該request是HTTP或HTTPS URL的請(qǐng)求(或者你自定義的網(wǎng)絡(luò)協(xié)議,并且確保該協(xié)議支持緩存)
    確保request請(qǐng)求是成功的(返回的status code為200-299)
  2. 返回的response是來(lái)自服務(wù)器端的,而非緩存中本身就有的
  3. 提供的NSURLRequest對(duì)象的緩存策略要允許進(jìn)行緩存
  4. 服務(wù)器返回的response中與緩存相關(guān)的header要允許緩存
  5. 該response的大小不能比提供的緩存空間大太多(比如你提供了一個(gè)磁盤(pán)緩存,那么response大小一定不能比磁盤(pán)緩存空間還要大5%)

NSURLSessionDownloadDelegate

1. URLSession:downloadTask:didFinishDownloadingToURL

首先看一下系統(tǒng)的API接口

/* Sent when a download task that has completed a download.  The delegate should 
 * copy or move the file at the given location to a new location as it will be 
 * removed when the delegate message returns. URLSession:task:didCompleteWithError: will
 * still be called.
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                              didFinishDownloadingToURL:(NSURL *)location;

看一下AFN中該方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (self.downloadTaskDidFinishDownloading) {
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            
            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }

            return;
        }
    }

    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

這里代碼都很簡(jiǎn)單,就不多說(shuō)了,這里也做了代理的轉(zhuǎn)發(fā)。

2. URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite

首先看一下系統(tǒng)的API接口

/* Sent periodically to notify the delegate of download progress. */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                                           didWriteData:(int64_t)bytesWritten
                                      totalBytesWritten:(int64_t)totalBytesWritten
                              totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

看一下AFN中該方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
    }

    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

這個(gè)方法和參數(shù)都很好理解,我就說(shuō)一點(diǎn),totalBytesExpectedToWrite這個(gè)參數(shù)表示期望收到的文件總字節(jié)數(shù),是由Content-Length header提供。如果沒(méi)有提供,默認(rèn)是NSURLSessionTransferSizeUnknown。

3. URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes

首先看一下系統(tǒng)的API接口

/* Sent when a download has been resumed. If a download failed with an
 * error, the -userInfo dictionary of the error will contain an
 * NSURLSessionDownloadTaskResumeData key, whose value is the resume
 * data. 
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                                      didResumeAtOffset:(int64_t)fileOffset
                                     expectedTotalBytes:(int64_t)expectedTotalBytes;

看一下AFN中該方法的實(shí)現(xiàn)

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
    }

    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}
  • 當(dāng)下載被取消或者失敗后重新恢復(fù)下載時(shí)調(diào)用。
  • 如果一個(gè)正在下載任務(wù)被取消或者失敗了,你可以請(qǐng)求一個(gè)resumeData對(duì)象(比如在userInfo字典中通過(guò)NSURLSessionDownloadTaskResumeData這個(gè)鍵來(lái)獲取到resumeData)并使用它來(lái)提供足夠的信息以重新開(kāi)始下載任務(wù)。
    隨后,你可以使用resumeData作為downloadTaskWithResumeData:或downloadTaskWithResumeData:completionHandler:的參數(shù)。當(dāng)你調(diào)用這些方法時(shí),你將開(kāi)始一個(gè)新的下載任務(wù)。一旦你繼續(xù)下載任務(wù),session會(huì)調(diào)用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask參數(shù)表示的就是新的下載任務(wù),這也意味著下載重新開(kāi)始了。
  • 注意:fileOffset這個(gè)參數(shù),如果文件緩存策略或者最后文件更新日期阻止重用已經(jīng)存在的文件內(nèi)容,那么該值為0。否則,該值表示當(dāng)前已經(jīng)下載data的偏移量。
  • 前面幾篇講解的那么多代理方法中,我們做的處理都是相對(duì)于這個(gè)sessionManager所有的request的。是公用的處理。而轉(zhuǎn)發(fā)的3個(gè)代理方法到AF的自定義deleagate,作用是需要對(duì)應(yīng)每個(gè)task去私有化處理。

轉(zhuǎn)發(fā)代理方法詳細(xì)解析

AFN自定義代理AFURLSessionManagerTaskDelegate中六個(gè)代理轉(zhuǎn)發(fā)的實(shí)現(xiàn)

1. URLSession:task:didCompleteWithError

先看一下該自定義代理方法的實(shí)現(xiàn)。

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

這里有幾點(diǎn)需要注意:

  • userInfo參數(shù)

這個(gè)參數(shù)是一個(gè)可變字典。

NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer";

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

這個(gè)userInfo用來(lái)存儲(chǔ)一些相關(guān)信息,存儲(chǔ)responseSerializer響應(yīng)解析對(duì)象。

  • 釋放mutableData內(nèi)存
//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;
}

當(dāng)我們不用self.mutableData這個(gè)對(duì)象的時(shí)候,就設(shè)置為nil,節(jié)省內(nèi)存,數(shù)據(jù)通過(guò)data傳出去了。

  • 有錯(cuò)誤error不為空
if (error) {
    //首先就是更新userInfo信息
    userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

    //然后就是在主線程進(jìn)行block回調(diào),并發(fā)出了一個(gè)通知AFNetworkingTaskDidCompleteNotification。
    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];
        });
    });
} 

有錯(cuò)誤的話,就按照上面的邏輯進(jìn)行處理。

  • 無(wú)錯(cuò)誤
dispatch_async(url_session_manager_processing_queue(), ^{

    //解析數(shù)據(jù)  驗(yàn)證response的有效性
    NSError *serializationError = nil;
    responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

    //如果是下載文件,那么responseObject為下載的路徑
    if (self.downloadFileURL) {
        responseObject = self.downloadFileURL;
    }

    if (responseObject) {
        //寫(xiě)入userInfo
        userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
    }

    if (serializationError) {
        //如果解析response有錯(cuò)誤,就更新userInfo
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
    }

    //結(jié)果的回調(diào)  在隊(duì)列url_session_manager_completion_group中進(jìn)行完成的處理
    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];
        });
    });
});

首先我們注意下隊(duì)列url_session_manager_processing_queue,是在一個(gè)并行隊(duì)列執(zhí)行的

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

無(wú)錯(cuò)誤時(shí)候的處理,都是在這個(gè)隊(duì)列中進(jìn)行處理的。

2. URLSession:dataTask:didReceiveData

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
    self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;

    [self.mutableData appendData:data];
}

這個(gè)很好理解,其實(shí)就是周期性的調(diào)用,在這個(gè)方法里面拼接了數(shù)據(jù)。

3. URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend

在向服務(wù)器上傳數(shù)據(jù)時(shí),代理周期性的接收

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}

回調(diào)來(lái)報(bào)告上傳進(jìn)度

4. URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite

在從服務(wù)器傳輸?shù)倪^(guò)程中,任務(wù)代理周期性的接收回調(diào)來(lái)報(bào)告?zhèn)鬏數(shù)倪M(jìn)度

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    
    self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
    self.downloadProgress.completedUnitCount = totalBytesWritten;
}

回調(diào)寫(xiě)入磁盤(pán)的進(jìn)度

5. URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
    
    self.downloadProgress.totalUnitCount = expectedTotalBytes;
    self.downloadProgress.completedUnitCount = fileOffset;
}

6. URLSession:downloadTask:didFinishDownloadingToURL

下載成功了被NSURLSession代理轉(zhuǎn)發(fā)到這里

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

這個(gè)大家是否注意到以前那個(gè)轉(zhuǎn)發(fā)到這里的NSURLSession的代理方法,里面也有關(guān)于路徑的處理,這里也進(jìn)行了處理,兩處都處理了,為什么呢?

轉(zhuǎn)發(fā)前的NSURLSession代理的下載路徑是所有request公用的下載路徑,一旦設(shè)置,所有的request都會(huì)下載到之前那個(gè)路徑。而這個(gè)是對(duì)應(yīng)的每個(gè)task的,每個(gè)task可以設(shè)置各自下載路徑。

數(shù)據(jù)解析

數(shù)據(jù)解析的類和協(xié)議

關(guān)于數(shù)據(jù)解析的類和接口,都存在AFURLResponseSerialization這個(gè)文件中,我們首先看一下這個(gè)文件中有多少類,以及它們是什么關(guān)系的。

  • @protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
  • @interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
  • @interface AFJSONResponseSerializer : AFHTTPResponseSerializer
  • @interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer
  • @interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
  • @interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
  • @interface AFImageResponseSerializer : AFHTTPResponseSerializer
  • @interface AFCompoundResponseSerializer : AFHTTPResponseSerializer

AFURLResponseSerialization協(xié)議

/**
 The response object decoded from the data associated with a specified response.

 @param response The response to be processed.
 @param data The response data to be decoded.
 @param error The error that occurred while attempting to decode the response data.

 @return The object decoded from the specified response data.
 */
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

根據(jù)服務(wù)器響應(yīng)中的細(xì)節(jié),AFURLResponseSerialization協(xié)議被一個(gè)對(duì)象采用,該對(duì)象將數(shù)據(jù)解碼為更有用的對(duì)象表示。 Response序列化器還可以對(duì)傳入響應(yīng)和數(shù)據(jù)執(zhí)行驗(yàn)證。例如,JSON響應(yīng)序列化器可以檢查可接受的狀態(tài)碼(2XX范圍)和內(nèi)容類型(application / json),將有效的JSON響應(yīng)解碼成對(duì)象

AFHTTPResponseSerializer

這個(gè)是所有其他解析類的父類,他遵守上面的AFURLResponseSerialization協(xié)議。

我們看一下協(xié)議在這個(gè)類中的實(shí)現(xiàn)

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //這里調(diào)用了一個(gè)方法,進(jìn)行了指定response和數(shù)據(jù)的驗(yàn)證。
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}

- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    BOOL responseIsValid = YES;
    NSError *validationError = nil;

    //就是如果response不是nil,并且response的類型是NSHTTPURLResponse。
    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;
}

在其基本實(shí)現(xiàn)中,此方法檢查可接受的狀態(tài)碼和內(nèi)容類型。 子類可能希望添加其他域特定的檢查。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容