iOS AFNetworking 源碼閱讀三

接下來(lái)我們來(lái)補(bǔ)充之前AFURLResponseSerialization這一塊是如何解析數(shù)據(jù)的

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

/**
 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;

@end

這實(shí)際是一個(gè)協(xié)議方法涩哟。而后面的解析類都是遵守這個(gè)協(xié)議方法赡麦,去做數(shù)據(jù)解析

AFHTTPResponseSerializer:
- (id)responseObjectForResponse:(NSHTTPURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

    return [[NSXMLParser alloc] initWithData:data];
}

方法調(diào)用了一個(gè)另外的方法之后,就把data返回來(lái)了

// 判斷是不是可接受類型和可接受code娩怎,不是則填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    // response是否合法標(biāo)識(shí)
    BOOL responseIsValid = YES;
    // 驗(yàn)證的error
    NSError *validationError = nil;
    // 如果存在且是NSHTTPURLResponse
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        //主要判斷自己能接受的數(shù)據(jù)類型和response的數(shù)據(jù)類型是否匹配吨岭,
        //如果有接受數(shù)據(jù)類型,如果不匹配response峦树,而且響應(yīng)類型不為空辣辫,數(shù)據(jù)長(zhǎng)度不為0
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {
            //進(jìn)入If塊說(shuō)明解析數(shù)據(jù)肯定是失敗的,這時(shí)候要把解析錯(cuò)誤信息放到error里魁巩。
            //如果數(shù)據(jù)長(zhǎng)度大于0急灭,而且有響應(yīng)url
            if ([data length] > 0 && [response URL]) {
                // 錯(cuò)誤信息字典,填充一些錯(cuò)誤信息
                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;
                }
                //生成錯(cuò)誤
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }
            //返回標(biāo)識(shí)
            responseIsValid = NO;
        }
        
        //判斷自己可接受的狀態(tài)嗎
        //如果和response的狀態(tài)碼不匹配谷遂,則進(jìn)入if塊
        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            //填寫(xiě)錯(cuò)誤信息字典
            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;
            }
             //生成錯(cuò)誤
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
            //返回標(biāo)識(shí)
            responseIsValid = NO;
        }
    }
    //給我們傳過(guò)來(lái)的錯(cuò)誤指針賦值
    if (error && !responseIsValid) {
        *error = validationError;
    }
    //返回是否錯(cuò)誤標(biāo)識(shí)
    return responseIsValid;
}

●簡(jiǎn)單來(lái)說(shuō)葬馋,這個(gè)方法就是來(lái)判斷返回?cái)?shù)據(jù)與咱們使用的解析器是否匹配,需要解析的狀態(tài)碼是否匹配肾扰。如果錯(cuò)誤畴嘶,則填充錯(cuò)誤信息,并且返回NO集晚,否則返回YES窗悯,錯(cuò)誤信息為nil。

●其中里面出現(xiàn)了兩個(gè)屬性值偷拔,一個(gè)acceptableContentTypes蒋院,一個(gè)acceptableStatusCodes,兩者在初始化的時(shí)候有給默認(rèn)值莲绰,我們也可以去自定義欺旧,但是如果給acceptableContentTypes定義了不匹配的類型,那么數(shù)據(jù)仍舊會(huì)解析錯(cuò)誤蛤签。

●而AFHTTPResponseSerializer僅僅是調(diào)用驗(yàn)證方法辞友,然后就返回了data。

AFJSONResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //先判斷是不是可接受類型和可接受code
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        //error為空,或者有錯(cuò)誤称龙,去函數(shù)里判斷留拾。
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            //返回空
            return nil;
        }
    }

    // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
    // See https://github.com/rails/rails/issues/1742
    
    //如果數(shù)據(jù)為空
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    //空則返回nil
    if (data.length == 0 || isSpace) {
        return nil;
    }
    
    NSError *serializationError = nil;
    // 不為空解析Jason
    id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

    if (!responseObject)
    {
        // 拿著json解析的error去填充錯(cuò)誤信息
        if (error) {
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
        return nil;
    }
    //判斷是否需要移除Null值
    if (self.removesKeysWithNullValues) {
        return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    //返回解析結(jié)果
    return responseObject;
}

接下來(lái)我們接著看調(diào)用的第一個(gè)方法

// 判斷是不是我們自己之前生成的錯(cuò)誤信息,是的話返回YES
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    // 判斷錯(cuò)誤域名和傳過(guò)來(lái)的域名是否一致茵瀑,錯(cuò)誤code是否一致
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    }
    //如果userInfo的NSUnderlyingErrorKey有值间驮,則在判斷一次躬厌。
    else if (error.userInfo[NSUnderlyingErrorKey]) {
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

這里可以注意马昨,我們這里傳過(guò)去的code和domain兩個(gè)參數(shù)分別為NSURLErrorCannotDecodeContentData、AFURLResponseSerializationErrorDomain扛施,這兩個(gè)參數(shù)是我們之前判斷response可接受類型和code時(shí)候自己去生成錯(cuò)誤的時(shí)候填寫(xiě)的鸿捧。

調(diào)用第二個(gè)方法

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    //分?jǐn)?shù)組和字典
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        //生成一個(gè)數(shù)組
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            //調(diào)用自己
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }
        //看我們解析類型是mutable還是非muatable,返回mutableArray或者array
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
             //value空則移除
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                //如果數(shù)組還是去調(diào)用自己
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

方法主要還是通過(guò)遞歸的形式實(shí)現(xiàn)。比較簡(jiǎn)單疙渣。

第三個(gè)


static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    if (!error) {
        return underlyingError;
    }

    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }

    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

方法主要是把json解析的錯(cuò)誤匙奴,賦值給我們需要返回給用戶的error上。比較簡(jiǎn)單

至此妄荔,AFJSONResponseSerializer就講完了

現(xiàn)在我們回到一開(kāi)始初始化的這行代碼上:

self.operationQueue.maxConcurrentOperationCount = 1;

首先我們要明確一個(gè)概念泼菌,這里的并發(fā)數(shù)僅僅是回調(diào)代理的線程并發(fā)數(shù)。而不是請(qǐng)求網(wǎng)絡(luò)的線程并發(fā)數(shù)啦租。請(qǐng)求網(wǎng)絡(luò)是由NSURLSession來(lái)做的哗伯,它內(nèi)部維護(hù)了一個(gè)線程池,用來(lái)做網(wǎng)絡(luò)請(qǐng)求篷角。它調(diào)度線程,基于底層的CFSocket去發(fā)送請(qǐng)求和接收數(shù)據(jù)焊刹。這些線程是并發(fā)的。

明確了這個(gè)概念之后恳蹲,我們來(lái)梳理一下AF的整個(gè)流程和線程的關(guān)系:
● 一開(kāi)始初始化sessionManager的時(shí)候虐块,一般都是在主線程。
● 然后我們調(diào)用get或者post等去請(qǐng)求數(shù)據(jù)嘉蕾,接著會(huì)進(jìn)行request拼接贺奠,AF代理的字典映射,progressKVO添加等等错忱,到NSUrlSessionresume之前這些準(zhǔn)備工作敞嗡,仍舊是在主線程中的。
● 然后我們調(diào)用NSUrlSessionresume航背,接著就跑到NSUrlSession內(nèi)部去對(duì)網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)請(qǐng)求了,在它內(nèi)部是多線程并發(fā)的去請(qǐng)求數(shù)據(jù)的喉悴。
● 緊接著數(shù)據(jù)請(qǐng)求完成后,回調(diào)回來(lái)在我們一開(kāi)始生成的并發(fā)數(shù)為1的NSOperationQueue中玖媚,這個(gè)時(shí)候會(huì)是多線程串行的回調(diào)回來(lái)的箕肃。
● 然后我們到返回?cái)?shù)據(jù)解析那一塊,我們自己又創(chuàng)建了并發(fā)的多線程今魔,去對(duì)這些數(shù)據(jù)進(jìn)行了各種類型的解析勺像。
●最后我們?nèi)绻凶远x的completionQueue障贸,則在自定義的queue中回調(diào)回來(lái),也就是分線程回調(diào)回來(lái)吟宦,否則就是主隊(duì)列篮洁,主線程中回調(diào)結(jié)束。

最后我們來(lái)解釋解釋為什么回調(diào)Queue要設(shè)置并發(fā)數(shù)為1:

我認(rèn)為AF這么做有以下兩點(diǎn)原因:
1.眾所周知殃姓,AF2.x所有的回調(diào)是在一條線程袁波,這條線程是AF的常駐線程,而這一條線程正是AF調(diào)度request的思想精髓所在蜗侈,所以第一個(gè)目的就是為了和之前版本保持一致篷牌。
2.因?yàn)楦硐嚓P(guān)的一些操作AF都使用了NSLock。所以就算Queue的并發(fā)數(shù)設(shè)置為n踏幻,因?yàn)槎嗑€程回調(diào)枷颊,鎖的等待,導(dǎo)致所提升的程序速度也并不明顯该面。反而多task回調(diào)導(dǎo)致的多線程并發(fā)夭苗,平白浪費(fèi)了部分性能。
而設(shè)置Queue的并發(fā)數(shù)為1隔缀,(注:這里雖然回調(diào)Queue的并發(fā)數(shù)為1题造,仍然會(huì)有不止一條線程,但是因?yàn)槭谴谢卣{(diào)蚕泽,所以同一時(shí)間晌梨,只會(huì)有一條線程在操作AFUrlSessionManager的那些方法。)至少回調(diào)的事件须妻,是不需要多線程并發(fā)的仔蝌。回調(diào)沒(méi)有了NSLock的等待時(shí)間荒吏,所以對(duì)時(shí)間并沒(méi)有多大的影響敛惊。(注:但是還是會(huì)有多線程的操作的,因?yàn)樵O(shè)置剛開(kāi)始調(diào)起請(qǐng)求的時(shí)候绰更,是在主線程的瞧挤,而回調(diào)則是串行分線程。)

參考資料:
AFNetworking到底做了什么
結(jié)合最新的AF源碼做了適當(dāng)?shù)男薷?br> 文章所用到的注釋后的源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末儡湾,一起剝皮案震驚了整個(gè)濱河市特恬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徐钠,老刑警劉巖癌刽,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡显拜,警方通過(guò)查閱死者的電腦和手機(jī)衡奥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)远荠,“玉大人矮固,你說(shuō)我怎么就攤上這事∑┐荆” “怎么了档址?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瘦赫。 經(jīng)常有香客問(wèn)我辰晕,道長(zhǎng)蛤迎,這世上最難降的妖魔是什么确虱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮替裆,結(jié)果婚禮上校辩,老公的妹妹穿的比我還像新娘。我一直安慰自己辆童,他們只是感情好宜咒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著把鉴,像睡著了一般故黑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庭砍,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天场晶,我揣著相機(jī)與錄音,去河邊找鬼怠缸。 笑死诗轻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的揭北。 我是一名探鬼主播扳炬,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搔体!你這毒婦竟也來(lái)了恨樟?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤疚俱,失蹤者是張志新(化名)和其女友劉穎劝术,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夯尽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年瞧壮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匙握。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咆槽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出圈纺,到底是詐尸還是另有隱情秦忿,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布蛾娶,位于F島的核電站灯谣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛔琅。R本人自食惡果不足惜胎许,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望罗售。 院中可真熱鬧辜窑,春花似錦、人聲如沸寨躁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)职恳。三九已至所禀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間放钦,已是汗流浹背色徘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留最筒,地道東北人贺氓。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像床蜘,于是被迫代替她去往敵國(guó)和親辙培。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,383評(píng)論 8 265
  • 接著上一篇的內(nèi)容往下講,如果沒(méi)看過(guò)上一篇內(nèi)容可以點(diǎn)這: AFNetworking到底做了什么? 之前我們講到NSU...
    涂耀輝閱讀 21,168評(píng)論 28 160
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,101評(píng)論 1 32
  • OC語(yǔ)言基礎(chǔ) 1.類與對(duì)象 類方法 OC的類方法只有2種:靜態(tài)方法和實(shí)例方法兩種 在OC中丹擎,只要方法聲明在@int...
    奇異果好補(bǔ)閱讀 4,271評(píng)論 0 11
  • 我曾在網(wǎng)上看到有人說(shuō) 人生感到最失敗的時(shí)候尾抑,每天要買(mǎi)日用品買(mǎi)餐食歇父,只敢去離家最近的便利店,恨不得戴上口罩和帽子再愈,生...
    會(huì)咬人的撲克牌閱讀 1,143評(píng)論 0 1