AFNetworking - AFURLSessionManager源碼解析(一)

AFURLSessionManager這個類是AFNetworking最核心的類锰镀,它的任務就是管理會話,響應會話中的每一個任務

我們首先來梳理一下一次完整的網(wǎng)絡(luò)請求及其響應,看看它是走哪些方法,按照什么順序來走,我們就看一次最簡單的GET請求

首先显设,我們還記得在最外層AFHTTPSessionManager類中發(fā)起了請求任務

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

它調(diào)用了它的父類也就是AFURLSessionManager的任務創(chuàng)建方法

- (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 {
    
    //根據(jù)請求創(chuàng)建一個任務
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
    //把創(chuàng)建的每一個任務交給一個AFURLSessionManagerTaskDelegate類來管理
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

addDelegateForDataTask方法為每一個任務創(chuàng)建一個任務代理類,由它來全權(quán)獨立管理每個任務的狀態(tài)辛辨,進度和完成的回調(diào)捕捂,并且把它和每個任務進行一一對應,以便在會話中能正確響應每個任務的代理回調(diào)方法

這段代碼我認為也是最能表現(xiàn)AFNetworking這個框架精髓的一段代碼斗搞,它體現(xiàn)了AFNetworking的核心思想指攒,公用session,沒有必要進行多余的TCP連接僻焚,在一個會話里面創(chuàng)建多個任務允悦,獨立管理每個任務,這樣既能節(jié)約資源也能很大程度縮短網(wǎng)絡(luò)請求的時間虑啤,提高會話任務的效率

- (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
{
    //根據(jù)任務來創(chuàng)建一個任務代理類
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
    //將會話管理類也傳給任務代理類隙弛,以便使用會話管理中的方法
    delegate.manager = self;
    //完成任務的代碼塊也要傳給它
    delegate.completionHandler = completionHandler;

    //任務描述
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    //將每一個任務和它對應的任務管理類進行鍵值對關(guān)系綁定
    [self setDelegate:delegate forTask:dataTask];

    //上傳進度代碼塊
    delegate.uploadProgressBlock = uploadProgressBlock;
    //下載進度代碼塊
    delegate.downloadProgressBlock = downloadProgressBlock;
}

這里就是為任務和任務代理類關(guān)系進行綁定的方法,以任務的ID為鍵狞山,AFURLSessionManagerTaskDelegate對象為值全闷,這里還用了鎖,來保證綁定過程的安全

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

    //這里用鎖來保證線程安全萍启,mutableTaskDelegatesKeyedByTaskIdentifier經(jīng)常需要被改動
    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

發(fā)起請求后首先會進行https認證总珠,前面說的安全策略類AFSecurityPolicy就是在這里進行認證

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

    if (self.authenticationChallengeHandler) {
        id result = self.authenticationChallengeHandler(session, task, challenge, completionHandler);
        if (result == nil) {
            return;
        } else if ([result isKindOfClass:NSError.class]) {
            objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN);
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        } else if ([result isKindOfClass:NSURLCredential.class]) {
            credential = result;
            disposition = NSURLSessionAuthChallengeUseCredential;
        } else if ([result isKindOfClass:NSNumber.class]) {
            disposition = [result integerValue];
            NSAssert(disposition == NSURLSessionAuthChallengePerformDefaultHandling || disposition == NSURLSessionAuthChallengeCancelAuthenticationChallenge || disposition == NSURLSessionAuthChallengeRejectProtectionSpace, @"");
            evaluateServerTrust = disposition == NSURLSessionAuthChallengePerformDefaultHandling && [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
        } else {
            @throw [NSException exceptionWithName:@"Invalid Return Value" reason:@"The return value from the authentication challenge handler must be nil, an NSError, an NSURLCredential or an NSNumber." userInfo:nil];
        }
    } else {
        evaluateServerTrust = [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
    }

    if (evaluateServerTrust) {
        if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
            disposition = NSURLSessionAuthChallengeUseCredential;
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        } else {
            objc_setAssociatedObject(task, AuthenticationChallengeErrorKey,
                                     [self serverTrustErrorForServerTrust:challenge.protectionSpace.serverTrust url:task.currentRequest.URL],
                                     OBJC_ASSOCIATION_RETAIN);
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }

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

服務器收到請求后返回數(shù)據(jù)屏鳍,這時候會響應NSURLSessionDataDelegate接收返回數(shù)據(jù)的方法,這里數(shù)據(jù)可能是分多次返回局服,可能會響應多次

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

    //根據(jù)任務找到對應的任務代理類
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    //調(diào)用代理類的數(shù)據(jù)接收方法钓瞭,和NSURLSessionDataDelegate的代理方法一致,只是在delegate里拼接可能多次返回的數(shù)據(jù)
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    
    //收到服務器返回數(shù)據(jù)回調(diào)的代碼塊
    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

AFURLSessionManagerTaskDelegate中的- (void)URLSession:dataTask:didReceiveData:方法

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    //下載進度設(shè)置
    self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
    self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
    //拼接數(shù)據(jù)
    [self.mutableData appendData:data];
}

接收服務器數(shù)據(jù)完成后腌逢,接著會響應NSURLSessionDataDelegate中的- (void)URLSession:task:didCompleteWithError:方法

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

再繼續(xù)調(diào)用delegate中的該方法降淮,請求服務器數(shù)據(jù)完成回調(diào)

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    //如果有認證質(zhì)詢錯誤也要返回
    error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error;
    //強引用超埋,防止被提前釋放
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    //創(chuàng)建一個信息字典
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    //存儲響應數(shù)據(jù)序列化信息
    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.
        //這時mutableData已經(jīng)沒有用搏讶,清空
        self.mutableData = nil;
    }

#if AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS
    if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) {
        //任務度量(包含請求響應時間等度量)
        if (self.sessionTaskMetrics) {
            userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics;
        }
    }
#endif
    //下載數(shù)據(jù)存儲的位置
    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);
            }
            //發(fā)送一條包含信息字典的通知
            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;
            }

            //返回數(shù)據(jù)保存到信息字典
            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) {
                    //網(wǎng)絡(luò)請求完成代碼塊
                    self.completionHandler(task.response, responseObject, serializationError);
                }
                //異步發(fā)送通知霍殴,帶有信息字典
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末媒惕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子来庭,更是在濱河造成了極大的恐慌妒蔚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件月弛,死亡現(xiàn)場離奇詭異肴盏,居然都是意外死亡,警方通過查閱死者的電腦和手機帽衙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門菜皂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人厉萝,你說我怎么就攤上這事恍飘。” “怎么了谴垫?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵章母,是天一觀的道長。 經(jīng)常有香客問我翩剪,道長乳怎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任前弯,我火速辦了婚禮蚪缀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘博杖。我一直安慰自己椿胯,他們只是感情好,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布剃根。 她就那樣靜靜地躺著哩盲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上廉油,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天惠险,我揣著相機與錄音,去河邊找鬼抒线。 笑死班巩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嘶炭。 我是一名探鬼主播抱慌,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼眨猎!你這毒婦竟也來了抑进?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤睡陪,失蹤者是張志新(化名)和其女友劉穎寺渗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兰迫,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡信殊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了汁果。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涡拘。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖须鼎,靈堂內(nèi)的尸體忽然破棺而出鲸伴,到底是詐尸還是另有隱情,我是刑警寧澤晋控,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布汞窗,位于F島的核電站,受9級特大地震影響赡译,放射性物質(zhì)發(fā)生泄漏仲吏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一蝌焚、第九天 我趴在偏房一處隱蔽的房頂上張望裹唆。 院中可真熱鬧,春花似錦只洒、人聲如沸许帐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽成畦。三九已至距芬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間循帐,已是汗流浹背框仔。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拄养,地道東北人离斩。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像瘪匿,于是被迫代替她去往敵國和親跛梗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355