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實例化

先看一個項目中進(jìn)行的GET請求腊徙,直接接入的就是AFN中的下面這個方法

**
 不需要進(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請求實現(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;
}

這里面五個參數(shù),很好理解檬某,請求的URL昧穿、參數(shù)、進(jìn)度block橙喘、成功block和失敗block时鸵。

下面我們看NSURLSessionDataTask這個類

/*
 * 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的實例化
- (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;
}

這里沒用到上傳饰潜,所以uploadProgress參數(shù)為nil,這種調(diào)用方式大家是不是很熟悉和簸,感覺很好彭雾,對了,SDWebImage下載圖像的接口就是這么調(diào)用的锁保,最后走的都是同一個方法薯酝,只是個別參數(shù)為nil或0浩村,最后在這個參數(shù)最全的方法里面做一些差別化的處理酿矢。大神都是這么寫代碼铐姚,不僅代碼邏輯清晰,而且調(diào)用和查看代碼也很方便。

這里做了兩個方面的工作:

  • 實例化NSMutableURLRequest請求對象帘睦。
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
  • 實例化NSURLSessionDataTask對象诡延,并調(diào)用下面方法返回該對象。
- (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ù)添加代理
//首先實例化AFURLSessionManagerTaskDelegate棺牧,并對改類內(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);
    //加鎖 需要保護的內(nèi)容放在中間识啦,讓數(shù)據(jù)更安全
    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [self addNotificationObserverForTask:task]; //通知 監(jiān)聽任務(wù)的開始和暫停
    [self.lock unlock];
}

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

  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這個屬性颓哮,需要設(shè)置為YES鸵荠。

@property (getter=isCancellable) BOOL cancellable;

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

- (void)cancel;

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

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

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

  • 開始
#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,開始進(jìn)行任務(wù)的處理

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

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

  1. 下載進(jìn)度

同上

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

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

- (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)聽

<!--添加通知監(jiān)聽-->
- (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. 開始
- (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];
            });
        }
    }
}

一起來看一下這里的邏輯蓝晒,首先就是取出任務(wù)notification.object腮出,然后判斷[task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks],這里self.taskDescriptionForSessionTasks的意思就是當(dāng)前實例化對象的地址芝薇。判斷如果是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];
            });
        }
    }
}

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

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

重寫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];
}

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

這樣如果沒實現(xiàn)這些我們自定義的Block也不會去回調(diào)這些代理机断。因為本身某些代理楷拳,只執(zhí)行了這些自定義的Block,如果Block都沒有賦值吏奸,那我們調(diào)用代理也沒有任何意義欢揖。

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中是實現(xiàn)她混,這里就舉一個例子,如下:

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

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

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

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

AFURLSessionManager中是有關(guān)NSURLSession的代理馒过,主要包含下面的幾個代理的實現(xiàn)晋涣。

AFURLSessionManagerTaskDelegate2

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

AFURLSessionManagerTaskDelegate

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

那么是如何轉(zhuǎn)發(fā)過去的呢,簡單的調(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;
}

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

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

NSURLSessionDelegate詳細(xì)解析

NSURLSession的代理在類AFURLSessionManager中實現(xiàn)钱磅,轉(zhuǎn)發(fā)到AFURLSessionManagerTaskDelegate自定義的代理中梦裂。下面開始會介紹這個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;

下面是代理方法的實現(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首先會先完成最后一個task,然后再調(diào)用URLSession:didBecomeInvalidWithError:代理方法褪迟,
如果你調(diào)用invalidateAndCancel方法來使session失效冗恨,那么該session會立即調(diào)用上面的代理方法。

這個就是該代理方法調(diào)用的時機牵咙。

這個代理方法做了兩個事情:

  • 調(diào)用了自定義的block - sessionDidBecomeInvalid
  • 發(fā)送了一個通知派近,但是沒有對象去監(jiān)聽,可能是作者故意留的接口吧洁桌,這個通知目前是沒有用到渴丸。

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;

下面看一下在該類中的實現(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);
    }
}

這里其實就是HTTPS認(rèn)證另凌,服務(wù)器向客戶端進(jìn)行質(zhì)詢和挑戰(zhàn)谱轨。這個類似的其實在SDWebImage中講過,這里也簡單的看一下吠谢。

web服務(wù)器接收到客戶端請求時土童,有時候需要先驗證客戶端是否為正常用戶,再決定是夠返回真實數(shù)據(jù)工坊。這種情況稱之為服務(wù)端要求客戶端接收挑戰(zhàn)(NSURLAuthenticationChallenge *challenge)献汗。接收到挑戰(zhàn)后,客戶端要根據(jù)服務(wù)端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定應(yīng)對這個挑戰(zhàn)的方法王污,而credential是客戶端生成的挑戰(zhàn)證書罢吃,注意只有challenge中認(rèn)證方法為NSURLAuthenticationMethodServerTrust的時候,才需要生成挑戰(zhàn)證書)昭齐。最后調(diào)用completionHandler回應(yīng)服務(wù)器端的挑戰(zhàn)尿招。

當(dāng)某個session使用SSL/TLS協(xié)議,第一次和服務(wù)器端建立連接的時候,服務(wù)器會發(fā)送給iOS客戶端一個證書就谜,此方法允許你的app驗證服務(wù)期端的證書鏈(certificate keychain)注:如果你沒有實現(xiàn)該方法怪蔑,該session會調(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中的實現(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)入隊的消息被發(fā)送出去后缆瓣,會調(diào)用該代理方法。
  • 在iOS中篮奄,當(dāng)一個后臺傳輸任務(wù)完成或者后臺傳輸時需要證書捆愁,而此時你的app正在后臺掛起,那么你的app在后臺會自動重新啟動運行窟却,并且這個app的UIApplicationDelegate會發(fā)送一個application:handleEventsForBackgroundURLSession:completionHandler:消息昼丑。該消息包含了對應(yīng)后臺的session的identifier,而且這個消息會導(dǎo)致你的app啟動夸赫。你的app隨后應(yīng)該先存儲completion handler菩帝,然后再使用相同的identifier創(chuàng)建一個background configuration,并根據(jù)這個background configuration創(chuàng)建一個新的session茬腿。這個新創(chuàng)建的session會自動與后臺任務(wù)重新關(guān)聯(lián)在一起呼奢。
  • 當(dāng)你的app獲取了一個URLSessionDidFinishEventsForBackgroundURLSession:消息,這就意味著之前這個session中已經(jīng)入隊的所有消息都轉(zhuǎn)發(fā)出去了切平,這時候再調(diào)用先前存取的completion handler是安全的握础,或者因為內(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中該代理方法的實現(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);
    }
}

這里悴品,主要做了下面幾個邏輯:

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

if (completionHandler) {
    completionHandler(redirectRequest);
}

這個方法是在服務(wù)器去重定向的時候定枷,才會被調(diào)用。
此方法只會在default session或者ephemeral session中調(diào)用届氢,而在background session中欠窒,session task會自動重定向。
補充一點退子,初始化NSURLSession對象的時候需要使用NSURLSessionConfiguration岖妄。有三個類工廠方法:

  • + defaultSessionConfiguration 返回一個標(biāo)準(zhǔn)的 configuration,具有相同的共享 NSHTTPCookieStorage寂祥,共享 NSURLCache 和共享NSURLCredentialStorage衣吠。
  • + ephemeralSessionConfiguration 返回一個預(yù)設(shè)配置,這個配置中不會對緩存壤靶,Cookie 和證書進(jìn)行持久性的存儲。這對于實現(xiàn)像秘密瀏覽這種功能來說是很理想的惊搏。
  • + backgroundSessionConfiguration:(NSString *)identifier 的獨特之處在于贮乳,它會創(chuàng)建一個后臺 session忧换。后臺 session 不同于常規(guī)的,普通的 session向拆,它甚至可以在應(yīng)用程序掛起亚茬,退出或者崩潰的情況下運行上傳和下載任務(wù)。初始化時指定的標(biāo)識符浓恳,被用于向任何可能在進(jìn)程外恢復(fù)后臺傳輸?shù)氖刈o進(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中該代理方法的實現(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);
    }
}

這里我們需要知道:

  • 這個是non-session-level級別的認(rèn)證
  • 多了一個參數(shù)task,然后調(diào)用我們自定義的Block會多回傳這個task作為參數(shù)颈将,這樣我們就可以根據(jù)每個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中該代理方法的實現(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);
    }
}

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

  • 如果task是由uploadTaskWithStreamedRequest:創(chuàng)建的,那么提供初始的request body stream時候會調(diào)用該代理方法晴圾。
  • 因為認(rèn)證挑戰(zhàn)或者其他可恢復(fù)的服務(wù)器錯誤颂砸,而導(dǎo)致需要客戶端重新發(fā)送一個含有body stream的request,這時候會調(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中該代理方法的實現(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ù)器人乓,會回調(diào)這個方法,通知已經(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中該代理方法的實現(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);
    }
}

這個代理就是task完成了的回調(diào)色罚,這里涉及到task和代理的綁定與接觸綁定,代碼不難账劲,就不多說了戳护。

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中該代理方法的實現(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這個block姑尺,通過傳入一個類型為NSURLSessionResponseDisposition的變量來決定該傳輸任務(wù)接下來該做什么:
  1. NSURLSessionResponseAllow 該task正常進(jìn)行
  2. NSURLSessionResponseCancel 該task會被取消
  3. NSURLSessionResponseBecomeDownload 會調(diào)用URLSession:dataTask:didBecomeDownloadTask:方法來新建一個download task以代替當(dāng)前的data task
  4. NSURLSessionResponseBecomeStream 轉(zhuǎn)成一個StreamTask
  • 當(dāng)你把添加content-type的類型為multipart/x-mixed-replace那么服務(wù)器的數(shù)據(jù)會分片的傳回來。然后這個方法是每次接受到對應(yīng)片響應(yīng)的時候會調(diào)被調(diào)用蝠猬。你應(yīng)該在這個函數(shù)中合理地處理先前的數(shù)據(jù)切蟋,否則會被新數(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中該代理方法的實現(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的時候榆芦,就會調(diào)用這個方法柄粹。

這個代理方法是被上面的代理方法觸發(fā)的,作用就是新建一個downloadTask匆绣,替換掉當(dāng)前的dataTask驻右。所以我們在這里做了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中該代理方法的實現(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ù)就會調(diào)用崎淳,會被反復(fù)調(diào)用堪夭,請求到的數(shù)據(jù)就在這被拼裝完整。
  • 這個方法和上面didCompleteWithError算是NSURLSession的代理中最重要的兩個方法。
  • 我們轉(zhuǎn)發(fā)了這個方法到AF的代理中去森爽,所以數(shù)據(jù)的拼接都是在AF的代理中進(jìn)行的恨豁。這也是情理中的,畢竟每個響應(yīng)數(shù)據(jù)都是對應(yīng)各個task爬迟,各個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中該代理方法的實現(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);
    }
}
  • 該方法的作用就是詢問data task或上傳任務(wù)(upload task)是否緩存response付呕。
  • 當(dāng)task接收到所有期望的數(shù)據(jù)后计福,session會調(diào)用此代理方法。
  • 當(dāng)task接收到所有期望的數(shù)據(jù)后徽职,session會調(diào)用此代理方法象颖。如果你沒有實現(xiàn)該方法,那么就會使用創(chuàng)建session時使用的configuration對象決定緩存策略活箕。這個代理方法最初的目的是為了阻止緩存特定的URLs或者修改NSCacheURLResponse對象相關(guān)的userInfo字典力麸。
  • 該方法只會當(dāng)request決定緩存response時候調(diào)用。作為準(zhǔn)則育韩,responses只會當(dāng)以下條件都成立的時候返回緩存:
  1. 該request是HTTP或HTTPS URL的請求(或者你自定義的網(wǎng)絡(luò)協(xié)議克蚂,并且確保該協(xié)議支持緩存)
    確保request請求是成功的(返回的status code為200-299)
  2. 返回的response是來自服務(wù)器端的,而非緩存中本身就有的
  3. 提供的NSURLRequest對象的緩存策略要允許進(jìn)行緩存
  4. 服務(wù)器返回的response中與緩存相關(guān)的header要允許緩存
  5. 該response的大小不能比提供的緩存空間大太多(比如你提供了一個磁盤緩存筋讨,那么response大小一定不能比磁盤緩存空間還要大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中該方法的實現(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];
    }
}

這里代碼都很簡單埃叭,就不多說了,這里也做了代理的轉(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中該方法的實現(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);
    }
}

這個方法和參數(shù)都很好理解赤屋,我就說一點,totalBytesExpectedToWrite這個參數(shù)表示期望收到的文件總字節(jié)數(shù)壁袄,是由Content-Length header提供类早。如果沒有提供,默認(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中該方法的實現(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ù)下載時調(diào)用涩僻。
  • 如果一個正在下載任務(wù)被取消或者失敗了,你可以請求一個resumeData對象(比如在userInfo字典中通過NSURLSessionDownloadTaskResumeData這個鍵來獲取到resumeData)并使用它來提供足夠的信息以重新開始下載任務(wù)栈顷。
    隨后逆日,你可以使用resumeData作為downloadTaskWithResumeData:或downloadTaskWithResumeData:completionHandler:的參數(shù)。當(dāng)你調(diào)用這些方法時萄凤,你將開始一個新的下載任務(wù)室抽。一旦你繼續(xù)下載任務(wù),session會調(diào)用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask參數(shù)表示的就是新的下載任務(wù)靡努,這也意味著下載重新開始了坪圾。
  • 注意:fileOffset這個參數(shù)晓折,如果文件緩存策略或者最后文件更新日期阻止重用已經(jīng)存在的文件內(nèi)容,那么該值為0神年。否則已维,該值表示當(dāng)前已經(jīng)下載data的偏移量。
  • 前面幾篇講解的那么多代理方法中已日,我們做的處理都是相對于這個sessionManager所有的request的。是公用的處理栅屏。而轉(zhuǎn)發(fā)的3個代理方法到AF的自定義deleagate飘千,作用是需要對應(yīng)每個task去私有化處理。

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

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

1. URLSession:task:didCompleteWithError

先看一下該自定義代理方法的實現(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];
                });
            });
        });
    }
}

這里有幾點需要注意:

  • userInfo參數(shù)

這個參數(shù)是一個可變字典护奈。

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

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

這個userInfo用來存儲一些相關(guān)信息,存儲responseSerializer響應(yīng)解析對象哥纫。

  • 釋放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這個對象的時候霉旗,就設(shè)置為nil,節(jié)省內(nèi)存蛀骇,數(shù)據(jù)通過data傳出去了厌秒。

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

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

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

  • 無錯誤
dispatch_async(url_session_manager_processing_queue(), ^{

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

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

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

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

    //結(jié)果的回調(diào)  在隊列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];
        });
    });
});

首先我們注意下隊列url_session_manager_processing_queue,是在一個并行隊列執(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;
}

無錯誤時候的處理个榕,都是在這個隊列中進(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];
}

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

3. URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend

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

- (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)來報告上傳進(jìn)度

4. URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite

在從服務(wù)器傳輸?shù)倪^程中苛让,任務(wù)代理周期性的接收回調(dià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)寫入磁盤的進(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];
            }
        }
    }
}

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

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

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

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

關(guān)于數(shù)據(jù)解析的類和接口眶拉,都存在AFURLResponseSerialization這個文件中,我們首先看一下這個文件中有多少類憔儿,以及它們是什么關(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é)議被一個對象采用谒臼,該對象將數(shù)據(jù)解碼為更有用的對象表示朝刊。 Response序列化器還可以對傳入響應(yīng)和數(shù)據(jù)執(zhí)行驗證。例如蜈缤,JSON響應(yīng)序列化器可以檢查可接受的狀態(tài)碼(2XX范圍)和內(nèi)容類型(application / json)畏邢,將有效的JSON響應(yīng)解碼成對象

AFHTTPResponseSerializer

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

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

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //這里調(diào)用了一個方法,進(jìn)行了指定response和數(shù)據(jù)的驗證败徊。
    [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;
}

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吃粒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拒课,更是在濱河造成了極大的恐慌徐勃,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件早像,死亡現(xiàn)場離奇詭異僻肖,居然都是意外死亡,警方通過查閱死者的電腦和手機卢鹦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門臀脏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冀自,你說我怎么就攤上這事揉稚。” “怎么了熬粗?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵搀玖,是天一觀的道長。 經(jīng)常有香客問我驻呐,道長灌诅,這世上最難降的妖魔是什么芳来? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮猜拾,結(jié)果婚禮上即舌,老公的妹妹穿的比我還像新娘。我一直安慰自己挎袜,他們只是感情好顽聂,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盯仪,像睡著了一般芜飘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上磨总,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音笼沥,去河邊找鬼蚪燕。 笑死,一個胖子當(dāng)著我的面吹牛奔浅,可吹牛的內(nèi)容都是我干的馆纳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼汹桦,長吁一口氣:“原來是場噩夢啊……” “哼鲁驶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舞骆,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤钥弯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后督禽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脆霎,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年狈惫,在試婚紗的時候發(fā)現(xiàn)自己被綠了睛蛛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胧谈,死狀恐怖忆肾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菱肖,我是刑警寧澤客冈,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蔑滓,受9級特大地震影響郊酒,放射性物質(zhì)發(fā)生泄漏遇绞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一燎窘、第九天 我趴在偏房一處隱蔽的房頂上張望摹闽。 院中可真熱鬧,春花似錦褐健、人聲如沸付鹿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舵匾。三九已至,卻和暖如春谁不,著一層夾襖步出監(jiān)牢的瞬間坐梯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工刹帕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吵血,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓偷溺,卻偏偏與公主長得像蹋辅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挫掏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容