AFNetWorking分析<二>

AFHTTPSessionManager

通常我們在運(yùn)用AFN框架進(jìn)行網(wǎng)絡(luò)請求時(shí),使用的都是AFHTTPSessionManager這個(gè)類沟于。AFHTTPSessionManager繼承于AFURLSessionManager,是對網(wǎng)絡(luò)請求的進(jìn)一步封裝兴猩。這個(gè)類將繁瑣的配置request旁趟、拼接formdata等工作進(jìn)行了封裝,僅僅提供GET皮假、POST呛梆、HEAD搪泳、PUTDELETE這幾個(gè)非常方便直觀的API姿现。
AFHTTPSessionManager相對于其父類,新添加了三個(gè)屬性baseURL肠仪、requestSerializerresponseSerializer备典。

請求器
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;

AFHTTPRequestSerializer這個(gè)類就是AFN框架對于網(wǎng)絡(luò)請求中request配置的封裝,它服從AFURLRequestSerialization協(xié)議,之后會(huì)詳細(xì)講解藤韵。在使用AFHTTPSessionManager時(shí)候,這個(gè)屬性是不能為nil的,在它的init方法中,是初始化為[AFHTTPRequestSerializer serializer],當(dāng)然也可以自己改變這個(gè)請求器,這個(gè)取決于你的后臺(tái)要接受什么類型的數(shù)據(jù),如果你的后臺(tái)是要接收json格式的請求那么就是[AFJSONRequestSerializer serializer]

響應(yīng)器
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;

AFHTTPResponseSerializer這個(gè)類是對網(wǎng)絡(luò)請求響應(yīng)的封裝,通過改變這個(gè)屬性,AFN框架可以自動(dòng)對請求下來的數(shù)據(jù)進(jìn)行解析熊经。例如,你請求下來的數(shù)據(jù)是json格式,那么將responseSerializer賦值成[AFJSONResponseSerializer serializer],于是你得到就是解析后的數(shù)據(jù)泽艘。這里要注意,如果在請求時(shí)出現(xiàn)3840的錯(cuò)誤碼,那就是你的responseSerializer有問題,很有可能請求下來的不是json串,而你指定它要json解析。
因?yàn)槲覀兤匠i_發(fā)常用到請求方式一般是兩種:GET镐依、POST匹涮。所以,我就以這兩個(gè)請求方式為例。
通常我們使用+ (instancetype)manager類方法,以此調(diào)用初始化方法

- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }

    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

在這里看到init方法,對請求器與響應(yīng)器進(jìn)行了初始化賦值槐壳。
之后我們會(huì)調(diào)用例如下面的方法

GET方法
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                      failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];

    [dataTask resume];

    return dataTask;
}

在這個(gè)方法內(nèi),調(diào)用

NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];

得到dataTask之后執(zhí)行resume方法然低。在這個(gè)方法里,首先會(huì)根據(jù)我們進(jìn)行網(wǎng)絡(luò)請求的方法來配置request。這時(shí)就用到了AFHTTPSessionManagerrequestSerializer屬性,通過屬性中的值來調(diào)用類的實(shí)例方法,之后,判斷是否配置錯(cuò)誤,如果配置錯(cuò)誤,則通過GCD在completionQueue這個(gè)隊(duì)列中會(huì)調(diào)出錯(cuò)誤信息,這里如果你沒有對這個(gè)屬性進(jìn)行賦值的話,它會(huì)選擇在主線程回調(diào)錯(cuò)誤信息务唐。配置好request之后就調(diào)用父類的網(wǎng)絡(luò)請求的方法,得到dataTask返回,在請求完成時(shí),執(zhí)行success或者failure的block雳攘。注意,這里得到的dataTask需要回調(diào)給外部,所以需要__block修飾。

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                         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 completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

在這些提供給外界使用的api里,有一個(gè)api是特殊的

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
     constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure

這個(gè)方法是我們進(jìn)行網(wǎng)絡(luò)上傳時(shí)使用的方法,我們可以看到它多加入了一個(gè)block參數(shù),這個(gè)block中有一個(gè)服從AFMultipartFormData協(xié)議的參數(shù)formData,從字面上我們就可以知道,如果我們需要上傳什么數(shù)據(jù)的話只需要往這個(gè)參數(shù)后面進(jìn)行拼接就可以了,事實(shí)上也的確如此枫笛。在這個(gè)POST方法中,調(diào)用了requestSerializer的另外一個(gè)用來配置上傳文件的request的方法吨灭。

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(NSDictionary *)parameters
                              constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError *__autoreleasing *)error

AFURLRequestSerialization中,構(gòu)建Multipart請求是占篇幅很大的一個(gè)功能,它也的確值得耗費(fèi)更多的代碼。在上一章,我已經(jīng)講了在iOS設(shè)備上傳文件時(shí)是multipart協(xié)議上傳,所以需要按照格式進(jìn)行配置request,這里就不在贅述了刑巧。在用NSURLRequest上傳文件時(shí),一般是兩種方法,一個(gè)是設(shè)置body,但是如果文件稍大的話,將會(huì)撐爆內(nèi)存喧兄。另外一種則是,創(chuàng)建一個(gè)臨時(shí)文件,將數(shù)據(jù)拼接進(jìn)去,然后將文件路徑設(shè)置為bodyStream,這樣就可以分片的上傳了。而AFN框架則是更進(jìn)一步的運(yùn)用邊傳邊拼的方式上傳文件,這無疑是更加高端也是更加繁瑣的方法啊楚。
這里通過constructingBodyWithBlock向使用者提供了一個(gè)AFStreamingMultipartFormData對象吠冤,調(diào)這個(gè)對象的append方法, AFStreamingMultipartFormData內(nèi)部把這些append的數(shù)據(jù)轉(zhuǎn)成不同類型的AFHTTPBodyPart,添加到自定義的 AFMultipartBodyStream 里恭理。最后把AFMultipartBodyStream賦給原來NSMutableURLRequest的bodyStream拯辙。NSURLConnection發(fā)送請求時(shí)會(huì)讀取這個(gè) bodyStream,在讀取數(shù)據(jù)時(shí)會(huì)調(diào)用這個(gè) bodyStream 的 -read:maxLength:方法颜价,AFMultipartBodyStream重寫了這個(gè)方法涯保,不斷讀取之前 append進(jìn)來的AFHTTPBodyPart 數(shù)據(jù)直到讀完饵较。

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }

    NSInteger totalNumberOfBytesRead = 0;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        } else {
            NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
            if (numberOfBytesRead == -1) {
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                totalNumberOfBytesRead += numberOfBytesRead;

                if (self.delay > 0.0f) {
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }
#pragma clang diagnostic pop

    return totalNumberOfBytesRead;
}

下圖就是multipart方式進(jìn)行上傳文件的request配置的步驟圖。


NSMutableURLRequest的構(gòu)建步驟

AFMultipartBodyStream

AFMultipartBodyStreamNSInputStream的子類,有人覺得是不是只要簡單的將這個(gè)類setHTTPBodyStream給request就可以了?事實(shí)上并不是這樣,用NSURLRequest 發(fā)出請求會(huì)導(dǎo)致 crash遭赂,提示

[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector

這是因?yàn)?code>NSURLRequest實(shí)際上接受的不是NSInputStream 對象循诉,而是 CoreFoundation 的 CFReadStreamRef 對象,因?yàn)?CFReadStreamRefNSInputStream 是 toll-free bridged撇他,可以自由轉(zhuǎn)換茄猫,但CFReadStreamRef 會(huì)用到 CFStreamScheduleWithRunLoop 這個(gè)方法,當(dāng)它調(diào)用到這個(gè)方法時(shí)困肩,object-c 的 toll-free bridging 機(jī)制會(huì)調(diào)用 object-c 對象 NSInputStream 的相應(yīng)函數(shù)划纽,這里就調(diào)用到了_scheduleInCFRunLoop:forMode:,若不實(shí)現(xiàn)這個(gè)方法就會(huì)crash锌畸。是不是覺得好繞坝铝印?的確,在學(xué)習(xí)這套框架的時(shí)候,我不停在的感慨大神就是大神,給你一種非常完美的感覺潭枣。其實(shí)AFN框架絕不僅僅是只有這幾個(gè)重點(diǎn),剩下的東西還有很多很多,例如還有AFURLResponseSerialization,和網(wǎng)絡(luò)請求驗(yàn)證證書的A'FSecurityPolicy比默。整體的架構(gòu)真的很漂亮,絕對是iOS開發(fā)工程師必需學(xué)習(xí)研究的著名框架之一。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盆犁,一起剝皮案震驚了整個(gè)濱河市命咐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谐岁,老刑警劉巖醋奠,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伊佃,居然都是意外死亡窜司,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門航揉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塞祈,“玉大人,你說我怎么就攤上這事迷捧≈郑” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵漠秋,是天一觀的道長。 經(jīng)常有香客問我抵屿,道長庆锦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任轧葛,我火速辦了婚禮搂抒,結(jié)果婚禮上艇搀,老公的妹妹穿的比我還像新娘。我一直安慰自己求晶,他們只是感情好焰雕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芳杏,像睡著了一般矩屁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爵赵,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天吝秕,我揣著相機(jī)與錄音,去河邊找鬼空幻。 笑死烁峭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秕铛。 我是一名探鬼主播约郁,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼但两!你這毒婦竟也來了棍现?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤镜遣,失蹤者是張志新(化名)和其女友劉穎己肮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悲关,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谎僻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寓辱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艘绍。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖秫筏,靈堂內(nèi)的尸體忽然破棺而出诱鞠,到底是詐尸還是另有隱情,我是刑警寧澤这敬,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布航夺,位于F島的核電站,受9級(jí)特大地震影響崔涂,放射性物質(zhì)發(fā)生泄漏阳掐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缭保。 院中可真熱鬧汛闸,春花似錦、人聲如沸艺骂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钳恕。三九已至别伏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間苞尝,已是汗流浹背畸肆。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宙址,地道東北人轴脐。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像抡砂,于是被迫代替她去往敵國和親大咱。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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