AFNetworking2.0源碼解析<二>

本篇我們繼續(xù)來看看AFNetworking的下一個(gè)模塊 — AFURLRequestSerialization。
AFURLRequestSerialization用于幫助構(gòu)建NSURLRequest,主要做了兩個(gè)事情:
1.構(gòu)建普通請(qǐng)求:格式化請(qǐng)求參數(shù),生成HTTP Header。
2.構(gòu)建multipart請(qǐng)求。
分別看看它在這兩點(diǎn)具體做了什么,怎么做的

1.構(gòu)建普通請(qǐng)求
A.格式化請(qǐng)求參數(shù)
一般我們請(qǐng)求都會(huì)按key=value的方式帶上各種參數(shù),GET方法參數(shù)直接加在URL上,POST方法放在body上,NSURLRequest沒有封裝好這個(gè)參數(shù)的解析,只能我們自己拼好字符串。AFNetworking提供了接口,讓參數(shù)可以是NSDictionary, NSArray, NSSet這些類型,再由內(nèi)部解析成字符串后賦給NSURLRequest。
轉(zhuǎn)化過程大致是這樣的:

@{
     @"name" : @"bang",
     @"phone": @{@"mobile": @"xx", @"home": @"xx"},
     @"families": @[@"father", @"mother"],
     @"nums": [NSSet setWithObjects:@"1", @"2", nil]
}
->
@[
     field: @"name", value: @"bang",
     field: @"phone[mobile]", value: @"xx",
     field: @"phone[home]", value: @"xx",
     field: @"families[]", value: @"father",
     field: @"families[]", value: @"mother",
     field: @"nums", value: @"1",
     field: @"nums", value: @"2",
]
->
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2

第一部分是用戶傳進(jìn)來的數(shù)據(jù),支持包含NSArray,NSDictionary,NSSet這三種數(shù)據(jù)結(jié)構(gòu)。
第二部分是轉(zhuǎn)換成AFNetworking內(nèi)自己的數(shù)據(jù)結(jié)構(gòu),每一個(gè)key-value對(duì)都用一個(gè)對(duì)象AFQueryStringPair表示,作用是最后可以根據(jù)不同的字符串編碼生成各自的key=value字符串。主要函數(shù)是AFQueryStringPairsFromKeyAndValue,詳見源碼注釋。
第三部分是最后生成NSURLRequest可用的字符串?dāng)?shù)據(jù),并且對(duì)參數(shù)進(jìn)行url編碼,在AFQueryStringFromParametersWithEncoding這個(gè)函數(shù)里。
最后在把數(shù)據(jù)賦給NSURLRequest時(shí)根據(jù)不同的HTTP方法分別處理,對(duì)于GET/HEAD/DELETE方法,把參數(shù)加到URL后面,對(duì)于其他如POST/PUT方法,把數(shù)據(jù)加到body上,并設(shè)好HTTP頭,告訴服務(wù)端字符串的編碼。

B.HTTP Header
AFNetworking幫你組裝好了一些HTTP請(qǐng)求頭,包括語言Accept-Language,根據(jù)[NSLocale preferredLanguages]方法讀取本地語言,告訴服務(wù)端自己能接受的語言。還有構(gòu)建User-Agent,以及提供Basic Auth認(rèn)證接口,幫你把用戶名密碼做base64編碼后放入HTTP請(qǐng)求頭。詳見源碼注釋。

C.其他格式化方式
HTTP請(qǐng)求參數(shù)不一定是要key=value形式,可以是任何形式的數(shù)據(jù),可以是json格式,蘋果的plist格式,二進(jìn)制protobuf格式等,AFNetworking提供了方法可以很容易擴(kuò)展支持這些格式,默認(rèn)就實(shí)現(xiàn)了json和plist格式。詳見源碼的類AFJSONRequestSerializer和AFPropertyListRequestSerializer。

2.構(gòu)建multipart請(qǐng)求
構(gòu)建Multipart請(qǐng)求是占篇幅很大的一個(gè)功能,AFURLRequestSerialization里2/3的代碼都是在做這個(gè)事。
A.Multipart協(xié)議介紹
Multipart是HTTP協(xié)議為web表單新增的上傳文件的協(xié)議,協(xié)議文檔是rfc1867,它基于HTTP的POST方法,數(shù)據(jù)同樣是放在body上,跟普通POST方法的區(qū)別是數(shù)據(jù)不是key=value形式,key=value形式難以表示文件實(shí)體,為此Multipart協(xié)議添加了分隔符,有自己的格式結(jié)構(gòu),大致如下:—AaB03xcontent-disposition: form-data; name=“name"

bang–AaB03xcontent-disposition: form-data; name=”pic”; filename=“content.txt”Content-Type: text/plain
… contents of bang.txt …–AaB03x–
以上表示數(shù)據(jù)name=bang以及一個(gè)文件,content.txt是文件名,… contents of bang.txt …是文件實(shí)體內(nèi)容。分隔符—AaB03x是可以自定義的,寫在HTTP頭部里:Content-type: multipart/form-data, boundary=AaB03x
每一個(gè)部分都有自己的頭部,表明這部分的數(shù)據(jù)類型以及其他一些參數(shù),例如文件名,普通字段的key。最后一個(gè)分隔符會(huì)多加兩橫,表示數(shù)據(jù)已經(jīng)結(jié)束:—AaB03x—。

B.實(shí)現(xiàn)
接下來說說怎樣構(gòu)造Multipart里的數(shù)據(jù),最簡(jiǎn)單的方式就是直接拼數(shù)據(jù),要發(fā)送一個(gè)文件,就直接把文件所有內(nèi)容讀取出來,再按上述協(xié)議加上頭部和分隔符,拼接好數(shù)據(jù)后扔給NSURLRequest的body就可以發(fā)送了,很簡(jiǎn)單。但這樣做是不可用的,因?yàn)槲募赡芎艽?,這樣拼數(shù)據(jù)把整個(gè)文件讀進(jìn)內(nèi)存,很可能把內(nèi)存撐爆了。

第二種方法是不把文件讀出來,不在內(nèi)存拼,而是新建一個(gè)臨時(shí)文件,在這個(gè)文件上拼接數(shù)據(jù),再把文件地址扔給NSURLRequest的bodyStream,這樣上傳的時(shí)候是分片讀取這個(gè)文件,不會(huì)撐爆內(nèi)存,但這樣每次上傳都需要新建個(gè)臨時(shí)文件,對(duì)這個(gè)臨時(shí)文件的管理也挺麻煩的。

第三種方法是構(gòu)建自己的數(shù)據(jù)結(jié)構(gòu),只保存要上傳的文件地址,邊上傳邊拼數(shù)據(jù),上傳是分片的,拼數(shù)據(jù)也是分片的,拼到文件實(shí)體部分時(shí)直接從原來的文件分片讀取。這方法沒上述兩種的問題,只是實(shí)現(xiàn)起來也沒上述兩種簡(jiǎn)單,AFNetworking就是實(shí)現(xiàn)這第三種方法,而且還更進(jìn)一步,除了文件,還可以添加多個(gè)其他不同類型的數(shù)據(jù),包括NSData,和InputStream。
AFNetworking里multipart請(qǐng)求的使用方式是這樣:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary
*parameters = @{
@"foo"
:
@"bar"
};

NSURL
*filePath = [
NSURL
fileURLWithPath:
@"[file://path/to/image.png](file://path/to/image.png)"
];

[manager POST:
@"[http://example.com/resources.json](http://example.com/resources.json)"
parameters:parameters constructingBodyWithBlock:^(
id
formData) {

    
[formData appendPartWithFileURL:filePath name:
@"image"
error:
nil
];

} success:^(AFHTTPRequestOperation *operation,
id
responseObject) {

    
NSLog
(
@"Success: %@"
, responseObject);

} failure:^(AFHTTPRequestOperation *operation,
NSError
*error) {

    
NSLog
(
@"Error: %@"
, error);

}];

這里通過constructingBodyWithBlock向使用者提供了一個(gè)AFStreamingMultipartFormData對(duì)象,調(diào)這個(gè)對(duì)象的幾種append方法就可以添加不同類型的數(shù)據(jù),包括FileURL/NSData/NSInputStream,AFStreamingMultipartFormData內(nèi)部把這些append的數(shù)據(jù)轉(zhuǎn)成不同類型的AFHTTPBodyPart,添加到自定義的AFMultipartBodyStream里。最后把AFMultipartBodyStream賦給原來NSMutableURLRequest的bodyStream。NSURLConnection發(fā)送請(qǐng)求時(shí)會(huì)讀取這個(gè)bodyStream,在讀取數(shù)據(jù)時(shí)會(huì)調(diào)用這個(gè)bodyStream的-read:maxLength:方法,AFMultipartBodyStream重寫了這個(gè)方法,不斷讀取之前append進(jìn)來的AFHTTPBodyPart數(shù)據(jù)直到讀完。
AFHTTPBodyPart封裝了各部分?jǐn)?shù)據(jù)的組裝和讀取,一個(gè)AFHTTPBodyPart就是一個(gè)數(shù)據(jù)塊。實(shí)際上三種類型(FileURL/NSData/NSInputStream)的數(shù)據(jù)在AFHTTPBodyPart都轉(zhuǎn)成NSInputStream,讀取數(shù)據(jù)時(shí)只需讀這個(gè)inputStream。inputStream只保存了數(shù)據(jù)的實(shí)體,沒有包括分隔符和頭部,AFHTTPBodyPart是邊讀取變拼接數(shù)據(jù),用一個(gè)狀態(tài)機(jī)確定現(xiàn)在數(shù)據(jù)讀取到哪一部份,以及保存這個(gè)狀態(tài)下已被讀取的字節(jié)數(shù),以此定位要讀的數(shù)據(jù)位置,詳見AFHTTPBodyPart的-read:maxLength:方法。
AFMultipartBodyStream封裝了整個(gè)multipart數(shù)據(jù)的讀取,主要是根據(jù)讀取的位置確定現(xiàn)在要讀哪一個(gè)AFHTTPBodyPart。AFStreamingMultipartFormData對(duì)外提供友好的append接口,并把構(gòu)造好的AFMultipartBodyStream賦回給NSMutableURLRequest,關(guān)系大致如下圖:


C.NSInputStream子類
NSURLRequest的setHTTPBodyStream接受的是一個(gè)NSInputStream*參數(shù),那我們要自定義inputStream的話,創(chuàng)建一個(gè)NSInputStream的子類傳給它是不是就可以了?實(shí)際上不行,這樣做后用NSURLRequest發(fā)出請(qǐng)求會(huì)導(dǎo)致crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector。
這是因?yàn)镹SURLRequest實(shí)際上接受的不是NSInputStream對(duì)象,而是CoreFoundation的CFReadStreamRef對(duì)象,因?yàn)镃FReadStreamRef和NSInputStream是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對(duì)象NSInputStream的相應(yīng)函數(shù),這里就調(diào)用到了_scheduleInCFRunLoop:forMode:,若不實(shí)現(xiàn)這個(gè)方法就會(huì)crash。詳見這篇文章。

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

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

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