AFNetWorking分析<二>

AFHTTPSessionManager

通常我們在運用AFN框架進行網(wǎng)絡(luò)請求時,使用的都是AFHTTPSessionManager這個類。AFHTTPSessionManager繼承于AFURLSessionManager,是對網(wǎng)絡(luò)請求的進一步封裝。這個類將繁瑣的配置request、拼接formdata等工作進行了封裝,僅僅提供GET、POSTHEAD、PUT、DELETE這幾個非常方便直觀的API。
AFHTTPSessionManager相對于其父類,新添加了三個屬性baseURL、requestSerializer、responseSerializer。

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

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

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

AFHTTPResponseSerializer這個類是對網(wǎng)絡(luò)請求響應(yīng)的封裝,通過改變這個屬性,AFN框架可以自動對請求下來的數(shù)據(jù)進行解析。例如,你請求下來的數(shù)據(jù)是json格式,那么將responseSerializer賦值成[AFJSONResponseSerializer serializer],于是你得到就是解析后的數(shù)據(jù)。這里要注意,如果在請求時出現(xiàn)3840的錯誤碼,那就是你的responseSerializer有問題,很有可能請求下來的不是json串,而你指定它要json解析。
因為我們平常開發(fā)常用到請求方式一般是兩種:GET、POST。所以,我就以這兩個請求方式為例。
通常我們使用+ (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)器進行了初始化賦值。
之后我們會調(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;
}

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

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

得到dataTask之后執(zhí)行resume方法。在這個方法里,首先會根據(jù)我們進行網(wǎng)絡(luò)請求的方法來配置request。這時就用到了AFHTTPSessionManagerrequestSerializer屬性,通過屬性中的值來調(diào)用類的實例方法,之后,判斷是否配置錯誤,如果配置錯誤,則通過GCD在completionQueue這個隊列中會調(diào)出錯誤信息,這里如果你沒有對這個屬性進行賦值的話,它會選擇在主線程回調(diào)錯誤信息。配置好request之后就調(diào)用父類的網(wǎng)絡(luò)請求的方法,得到dataTask返回,在請求完成時,執(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里,有一個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

這個方法是我們進行網(wǎng)絡(luò)上傳時使用的方法,我們可以看到它多加入了一個block參數(shù),這個block中有一個服從AFMultipartFormData協(xié)議的參數(shù)formData,從字面上我們就可以知道,如果我們需要上傳什么數(shù)據(jù)的話只需要往這個參數(shù)后面進行拼接就可以了,事實上也的確如此。在這個POST方法中,調(diào)用了requestSerializer的另外一個用來配置上傳文件的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請求是占篇幅很大的一個功能,它也的確值得耗費更多的代碼。在上一章,我已經(jīng)講了在iOS設(shè)備上傳文件時是multipart協(xié)議上傳,所以需要按照格式進行配置request,這里就不在贅述了。在用NSURLRequest上傳文件時,一般是兩種方法,一個是設(shè)置body,但是如果文件稍大的話,將會撐爆內(nèi)存。另外一種則是,創(chuàng)建一個臨時文件,將數(shù)據(jù)拼接進去,然后將文件路徑設(shè)置為bodyStream,這樣就可以分片的上傳了。而AFN框架則是更進一步的運用邊傳邊拼的方式上傳文件,這無疑是更加高端也是更加繁瑣的方法。
這里通過constructingBodyWithBlock向使用者提供了一個AFStreamingMultipartFormData對象,調(diào)這個對象的append方法, AFStreamingMultipartFormData內(nèi)部把這些append的數(shù)據(jù)轉(zhuǎn)成不同類型的AFHTTPBodyPart,添加到自定義的 AFMultipartBodyStream 里。最后把AFMultipartBodyStream賦給原來NSMutableURLRequest的bodyStream。NSURLConnection發(fā)送請求時會讀取這個 bodyStream,在讀取數(shù)據(jù)時會調(diào)用這個 bodyStream 的 -read:maxLength:方法,AFMultipartBodyStream重寫了這個方法,不斷讀取之前 append進來的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方式進行上傳文件的request配置的步驟圖。


NSMutableURLRequest的構(gòu)建步驟

AFMultipartBodyStream

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

[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,334評論 2 377

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