AFNetworking源碼探究(二) —— GET請求實現(xiàn)之NSURLSessionDataTask實例化(一)

版本記錄

版本號 時間
V1.0 2018.02.28

前言

我們做APP發(fā)起網(wǎng)絡(luò)請求,都離不開一個非常有用的框架AFNetworking,可以說這個框架的知名度已經(jīng)超過了蘋果的底層網(wǎng)絡(luò)請求部分,很多人可能不知道蘋果底層是如何發(fā)起網(wǎng)絡(luò)請求的,但是一定知道AFNetworking,接下來幾篇我們就一起詳細(xì)的解析一下這個框架。感興趣的可以看上面寫的幾篇。
1. AFNetworking源碼探究(一) —— 基本介紹

思路

這里很多類,我不會去每一個類單獨(dú)的去分析,那樣子輪著幾圈可能也不是很清晰,我會以一個簡單的例子入手,開始進(jìn)行分析,由點(diǎn)帶面,最后舉一反三給大家串起來。

先看一個項目中進(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. 根據(jù)客戶端請求序列化編碼的參數(shù)
 @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;

這里的參數(shù)很清晰就不多說了。


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ū)分。

2. NSURLSessionDataTask的實例化

下面我們接著看進(jìn)一步的調(diào)用

- (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) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        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請求對象。
  • 實例化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

下面我們就一起看一下,這兩個過程。

(a) 調(diào)試小技巧

這里大家應(yīng)該注意到有三行代碼

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

這個是做什么用的呢?在iOS開發(fā)過程中, 我們可能會碰到一些系統(tǒng)方法棄用, weak、循環(huán)引用、不能執(zhí)行之類的警告。 它的作用其實就是忽略一些沒用的警告用的,這里就是忽略?:條件表達(dá)式帶來的警告,具體的各種編譯器警告描述,可以參考這篇:各種編譯器的警告,具體使用也很簡單,先忽略什么樣的警告,就從上面的鏈接中查到,然后放在上面ignored的后面,不要忘記省略號哦~~,例如下邊就是忽略廢棄方法產(chǎn)生的警告。

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// 這里寫出現(xiàn)警告的代碼
#pragma clang diagnostic pop

這樣就消除了方法棄用的警告!

(b) 實例化NSMutableURLRequest請求對象

下面我們就看一下該請求的實例化方法,對應(yīng)下面這段代碼。

/**
 Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` 
 are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters 
 for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.
 使用`requestWithMethod:URLString:parameters:`&`multipartFormRequestWithMethod:URLString:parameters:constructBodyWithBlock:`
 創(chuàng)建的請求由一組使用此屬性指定的參數(shù)序列化的默認(rèn)標(biāo)頭構(gòu)造而成。 默認(rèn)情況下,它被設(shè)置為“AFHTTPRequestSerializer”的一個實例,
 該實例將GET,HEAD和DELETE請求的查詢字符串參數(shù)序列化,或者URL形式編碼HTTP消息體

 @warning `requestSerializer` must not be `nil`.
 */
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
/**
 The URL used to construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
 */
在requestWithMethod:URLString:parameters:和GET/POST等
便利方法中用于構(gòu)造相對路徑請求的URL。

@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

(c) 實例化NSURLSessionDataTask對象,并獲取和返回

  • 序列化是否錯誤的判斷

在實例化NSURLSessionDataTask對象之前,先判斷請求的序列化是否有錯誤,對應(yīng)的就是下邊這段代碼。

    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

如果上面構(gòu)建的請求序列化有錯誤,也就是serializationError不為nil,那么接著進(jìn)行判斷傳入的failure是否為nil,如果不為nil,那么就在隊列completionQueue中回調(diào)失敗,這個很好理解,請求序列化都有錯誤,還能指望下載下來東西嗎?直接進(jìn)行錯誤的回調(diào)。默認(rèn)在完成隊列completionQueue中回調(diào),如果該completionQueue隊列為空,那么就在主隊列進(jìn)行回調(diào),這里是一個三目運(yùn)算符,failure回調(diào)第一個參數(shù)為nil,這里還沒實例化NSURLSessionDataTask對象,當(dāng)然為nil了,其實還是很好理解的。

/**
 The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
 */
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
  • NSURLSessionDataTask對象實例化

下面就是該對象的實例化,主要對應(yīng)下邊這段代碼。

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

這里可以看見,又調(diào)用了別的方法,并在回調(diào)中進(jìn)行成功和失敗的回調(diào)。

failure(dataTask, error);
success(dataTask, responseObject);

3. dataTaskWithRequest:...方法的調(diào)用

這里調(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;
}

這里我們一起來看一下都做了什么。

(a) 實例化相關(guān)的iOS8的BUG

首先看一下函數(shù),如下:

#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0

static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
        // Fix of bug
        // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
        // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
        dispatch_sync(url_session_manager_creation_queue(), block);
    } else {
        block();
    }
}
static dispatch_queue_t url_session_manager_creation_queue() {
    static dispatch_queue_t af_url_session_manager_creation_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
    });

    return af_url_session_manager_creation_queue;
}

這里,首先判斷如果NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug,那么就在串行隊列中執(zhí)行block。這里可能大家要問了,為什么要這么判斷,有什么用?其實NSFoundationVersionNumber這個是獲取系統(tǒng)版本的另外一種方式,這里標(biāo)注這么做是因為iOS8出現(xiàn)的一個BUG。

這里寫的很清晰了,就是為了防止iOS 8在并發(fā)隊列上創(chuàng)建任務(wù)時,可能會調(diào)用錯誤的completionHandlers。當(dāng)任務(wù)返回一個重復(fù)的taskIdentifier時,先前的completionHandler被清除并替換為新的。 如果第一個請求的數(shù)據(jù)在第二個請求的數(shù)據(jù)之前返回,那么將針對第二個completionHandler調(diào)用第一個響應(yīng)。

我們在這個block里面回調(diào)做了什么?

dataTask = [self.session dataTaskWithRequest:request];

/* Creates a data task with the given request.  The request may have a body stream. */
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;

這個其實就是調(diào)用到蘋果系統(tǒng)的方法里面了,根據(jù)request創(chuàng)建對應(yīng)的任務(wù)NSURLSessionDataTask。

(b) 為指定的任務(wù)添加代理

下面我們就看一下為指定的任務(wù)NSURLSessionDataTask是如何添加代理的。

// 調(diào)用
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

// 實現(xiàn)
- (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] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

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

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

上面首先實例化AFURLSessionManagerTaskDelegate,并對改類內(nèi)部的屬性進(jìn)行賦值,并調(diào)用下面的方法為task設(shè)置delegate。

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

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

1)斷言

這里首先要簡單的說一下一中斷言的形式NSParameterAssert(),它的作用就是括號里面參數(shù)不為nil就繼續(xù)向下執(zhí)行,如果為nil就觸發(fā)斷言崩潰。我用下面代碼進(jìn)行了測試。

NSParameterAssert(nil);

看一下輸出結(jié)果

2018-02-28 09:46:30.336229+0800 JJWebImage[3893:1141549] *** Assertion failure in -[ViewController viewDidLoad], /Users/mac/Desktop/JJWebImage/JJWebImage/ViewController.m:42
2018-02-28 09:46:30.336755+0800 JJWebImage[3893:1141549] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: nil'

它其實是NSAssert的預(yù)編譯,這樣說明了為什么上面會輸出那樣的異常信息。

#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)

2)加鎖

由于多線程可能帶來的數(shù)據(jù)不安全性,這里進(jìn)行了加鎖

[self.lock lock];
... ... 

[self.lock unlock];

需要保護(hù)的內(nèi)容放在中間,讓數(shù)據(jù)更安全。

首先,實例化一個可變字典,key為taskIdentifier,vlaue就是該任務(wù)的代理。

@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier;

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

然后,就是代理為任務(wù)添加進(jìn)度,這個會在后面分篇詳述。

[delegate setupProgressForTask:task];

最后,就是為任務(wù)添加通知觀察

[self addNotificationObserverForTask:task];
- (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];
}

其實就是監(jiān)聽任務(wù)的開始和暫停,這個也會在后面分篇詳述。

后記

本篇從GET請求入口開始,進(jìn)行深入分析,包括實例化NSURLSessionDataTask的過程以及為任務(wù)添加代理和通知觀察。下一篇會看一下代理和進(jìn)度之間的關(guān)系以及通知的作用。

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

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

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