說實(shí)話森渐,AFN是個(gè)博大精深的東西做入,作為一款全世界都在使用的iOS框架想要一下子就參透也是不大可能的,但是同衣,我還是要契而不舍的鉆研他母蛛,參透他!
1.AFN的使用
要研究一款框架的實(shí)現(xiàn)乳怎,首先要了解這個(gè)框架的基本使用彩郊,在開發(fā)中我們用到AFN主要就是在發(fā)送網(wǎng)絡(luò)請(qǐng)求、下載數(shù)據(jù)蚪缀、上傳數(shù)據(jù)秫逝。
用AFN可以非常方便的發(fā)送get請(qǐng)求和post請(qǐng)求,方法相同询枚,只是需要修改一下參數(shù)和函數(shù)名违帆。
AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];
//我們也可以把這部分加粗的參數(shù)存放在一個(gè)字典里,然后通過parameters這個(gè)參數(shù)傳遞金蜀。
[manager GET:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {子線程執(zhí)行
? ? } success:^(NSURLSessionDataTask * _Nonnull task, id? _Nullable responseObject) {
? ? ? ? //task.response是請(qǐng)求頭
? ? ? ? NSLog(@"%@",responseObject);
? ? ? ? NSLog(@"success");主線程執(zhí)行
? ? } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
? ? ? ? NSLog(@"failure");主線程執(zhí)行
? ? }];
post方法也是一樣的刷后,只不過把請(qǐng)求的參數(shù)存放到一個(gè)字典里傳遞。
下載文件的方法:
-(void)download{
? ? AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];
? ? NSURL* url = [[NSURL alloc] initWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
? ? NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url];
? ? NSURLSessionDownloadTask* task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) { ? ? ? ? NSLog(@"%f",1.0*downloadProgress.completedUnitCount/downloadProgress.totalUnitCount);??:這里用downloadProgress自帶的兩個(gè)參數(shù)相除就可以得到下載進(jìn)度渊抄,在2.x版本的時(shí)候沒有這個(gè)回調(diào)參數(shù)尝胆,需要使用kvo監(jiān)聽下載進(jìn)度的改變。
? ? } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
??:這個(gè)block是有返回值的护桦,需要返回一個(gè)NSURL含衔,在這個(gè)block塊里我們需要把下載到的文件從臨時(shí)路徑剪切到指定的目標(biāo)路徑。targetPath就是AFN為我們自動(dòng)下載到的臨時(shí)路徑二庵,我們需要提供一個(gè)目標(biāo)路徑filePath贪染,AFN會(huì)自動(dòng)把下載下來的文件從臨時(shí)文件夾剪切到filePath路徑下
? ? ? ? NSString* filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:response.suggestedFilename];
? ? ? ? NSLog(@"1-----%@",filePath);
? ? ? ? NSLog(@"2-----%@",[NSThread currentThread]);//這段代碼是在子線程執(zhí)行的
? ? ? ? return [NSURL fileURLWithPath:filePath];
? ? } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
? ? ? ? NSLog(@"download finish");
? ? ? ? NSLog(@"2-----%@",filePath);
? ? ? ? NSLog(@"2-----%@",[NSThread currentThread]);主線程執(zhí)行
? ? }];
? ? [task resume];需要手動(dòng)開啟執(zhí)行任務(wù)
}
上傳文件的方法:
-(void)upload{
????????AFHTTPSessionManager* manager = [AFHTTPSessionManager manager]; ? ? ? ? ? ? ? ? ? ? ? ? ?
??:這里的上傳文件的方法不是什么什么upload,而是一種post方法
????????[manager POST:@"http://120.25.226.186:32812/upload" parameters:nil constructingBodyWithBlock:^(id _Nonnull formData) {
? ? ? ? [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"/Users/apple/Documents/我自己 2.jpg"] name:@"my" error:nil];
? ? } progress:^(NSProgress * _Nonnull uploadProgress) { ? ?????NSLog(@"%f",1.0*uploadProgress.completedUnitCount/uploadProgress.totalUnitCount);//上傳的操作是開啟子線程并發(fā)執(zhí)行的
? ? } success:^(NSURLSessionDataTask * _Nonnull task, id? _Nullable responseObject) {
? ? ? ? NSLog(@"success");//下載成功的回調(diào)在主線程執(zhí)行
? ? } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
? ? ? ? NSLog(@"fail");//下載失敗的回調(diào)在主線程執(zhí)行
? ? }];
}
2.框架的結(jié)構(gòu)
AFNetworking主要分為五個(gè)模塊:
網(wǎng)絡(luò)通信模塊(AFURLSessionManager催享、AFHTTPSessionManger)
網(wǎng)絡(luò)狀態(tài)監(jiān)聽模塊(Reachability)
網(wǎng)絡(luò)通信安全策略模塊(Security)
網(wǎng)絡(luò)通信信息序列化/反序列化模塊(Serialization)
對(duì)于iOS UIKit庫(kù)的擴(kuò)展(UIKit)
3.對(duì)不同類型數(shù)據(jù)的解析
如果我們請(qǐng)求的數(shù)據(jù)是json類型的杭隙,那么正確返回的數(shù)據(jù)也是json類型的,這個(gè)時(shí)候我們不需要修改數(shù)據(jù)的解析方案因妙,直接請(qǐng)求就可以了痰憎。
如果我們請(qǐng)求的數(shù)據(jù)是xml類型的票髓,這個(gè)時(shí)候就需要修改數(shù)據(jù)的解析方案了。
-(void)getXML{
AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];
??:設(shè)置解析方法:manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
[manager GET:@"http://120.25.226.186:32812/login2?username=123&pwd=123&type=XML" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) { ? ? ? ? ? ?`????????????NSLog(@"%f",1.0*downloadProgress.completedUnitCount/downloadProgress.totalUnitCount); }
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { //這個(gè)時(shí)候的responseObject是NSXMLParser類型的信殊,無法直接拿到數(shù)據(jù) NSXMLParser* parser = (NSXMLParser*)responseObject;把responseObject強(qiáng)制轉(zhuǎn)化為NSXMLParser類型的對(duì)象炬称,之后用代理方法進(jìn)行解析汁果。
parser.delegate = self;
[parser parse]; }
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"fail---%@",error); }];}
//用代理方法解析數(shù)據(jù)
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
? ? NSLog(@"%@---%@",elementName,attributeDict);
}
如果請(qǐng)求到的數(shù)據(jù)既不是json類型的涡拘,也不是xml類型的,這個(gè)時(shí)候就需要使用這種解析方法:
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
請(qǐng)求到的responseObject是NSData類型的据德,可以對(duì)他進(jìn)行下一步的轉(zhuǎn)化鳄乏。其實(shí)這種方法是默認(rèn)的,可以解析所有類型的數(shù)據(jù)棘利。
4.為什么要用AFN框架
說到為什么要用這個(gè)框架橱野,我們就必須要知道在沒有使用這個(gè)框架之前用iOS原生的機(jī)制來處理網(wǎng)絡(luò)請(qǐng)求會(huì)發(fā)生什么問題。
首先我們來說最原始的NSURLConnection是怎么發(fā)送網(wǎng)絡(luò)請(qǐng)求的善玫。
發(fā)送網(wǎng)絡(luò)請(qǐng)求的方法可以分為兩類水援,異步發(fā)送請(qǐng)求和同步發(fā)送請(qǐng)求。
異步發(fā)送網(wǎng)絡(luò)請(qǐng)求有兩種方法茅郎,一種是sendAsynchronousRequest蜗元,一種是用delegate。
同步發(fā)送網(wǎng)絡(luò)請(qǐng)求用sendSynchronousRequest系冗。
如果是使用sendSynchronousRequest或者sendAsynchronousRequest方法奕扣,我們是用block來處理回調(diào)的,可以指定NSOperationQueue指定回調(diào)方法在哪個(gè)隊(duì)列執(zhí)行掌敬。
如果是在主線程用代理方法發(fā)送網(wǎng)絡(luò)請(qǐng)求惯豆,那么代理方法也是在主線程執(zhí)行的,如果要讓代理方法在子線程執(zhí)行可以開子線程發(fā)送網(wǎng)絡(luò)請(qǐng)求奔害,或者設(shè)置setDelegateQueue楷兽。
那么只要是遇到了發(fā)送網(wǎng)絡(luò)請(qǐng)求的部分,我們就需要讓UIViewController去遵守協(xié)議华临,實(shí)現(xiàn)代理方法拄养。這樣做的壞處就是,網(wǎng)絡(luò)層和控制器寫在了一塊兒银舱,沒有剝離開瘪匿,也沒有實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的統(tǒng)一管理。
基于NSURLConnection的AFN做了什么呢寻馏?
就是把網(wǎng)絡(luò)請(qǐng)求和回調(diào)進(jìn)行了統(tǒng)一的管理棋弥,不需要為每一個(gè)請(qǐng)求開啟子線程,這里的做法是開啟一條常駐子線程處理所有的請(qǐng)求和響應(yīng)诚欠。
那么NSURLSession對(duì)比起NSURLConnection有哪些不同呢顽染?
參考文章:從 NSURLConnection 到 NSURLSession
?首先我們討論一個(gè)問題漾岳,就是使用NSURLConnection容易造成什么問題?
NSURLConnectoin只隱藏了單個(gè)網(wǎng)絡(luò)請(qǐng)求的線程的相關(guān)操作粉寞,并沒有提供接口來解決多個(gè)網(wǎng)絡(luò)請(qǐng)求時(shí)多個(gè)線程的管理問題尼荆。
并且NSURLConnection不是基于HTTP/2協(xié)議的,若使用NSURLConnection發(fā)起請(qǐng)求則每次請(qǐng)求都需要經(jīng)過三次握手過程唧垦。而使用了session之后捅儒,使用同一個(gè)session中的task訪問數(shù)據(jù),不用每次都實(shí)現(xiàn)三次握手振亮,復(fù)用之前的連接可以加快訪問速度巧还。
5.源碼分析
這里有一篇非常特別的源碼分析,他不是在分析AFN這個(gè)源碼的實(shí)現(xiàn)流程坊秸,而是總結(jié)了自己在學(xué)習(xí)這個(gè)源碼的過程中學(xué)到了哪些思想麸祷。
AFNetworking 3.0 源碼解讀 總結(jié)(干貨)(上)
重要的方法1:AFURLSessionManager的initWithSessionConfiguration:方法
????self.sessionConfiguration = configuration;
? ? self.operationQueue = [[NSOperationQueue alloc] init];
? ? self.operationQueue.maxConcurrentOperationCount = 1;
? ? self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];??:這里設(shè)置的代理操作隊(duì)列的最大并發(fā)數(shù)為1,為了讓所有的請(qǐng)求的發(fā)起和等待網(wǎng)絡(luò)響應(yīng)都在同一個(gè)線程褒搔,不需要為每一個(gè)請(qǐng)求創(chuàng)建一個(gè)線程阶牍。
self.responseSerializer = [AFJSONResponseSerializer serializer]; self.securityPolicy = [AFSecurityPolicy defaultPolicy];
使用代理方法完成進(jìn)一步的操作。
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
? ? AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];在這個(gè)方法里星瘾,給dataTask添加了一個(gè)代理走孽,這個(gè)代理負(fù)責(zé)處理后續(xù)的數(shù)據(jù)拼接和數(shù)據(jù)操作。在實(shí)現(xiàn)delegateForTask:這個(gè)方法的時(shí)候死相,上鎖融求,并給delegate一個(gè)唯一的標(biāo)識(shí),防止不同的task使用同一個(gè)delegate算撮。
? ? [delegate URLSession:session dataTask:dataTask didReceiveData:data];
? ? if (self.dataTaskDidReceiveData) {
? ? ? ? self.dataTaskDidReceiveData(session, dataTask, data);
? ? }
}
數(shù)據(jù)傳輸完成后生宛,調(diào)用didCompleteWithError:(NSError *)error方法:
- (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];把task對(duì)應(yīng)的代理從代理字典里移除
? ? }
? ? if (self.taskDidComplete) {
? ? ? ? self.taskDidComplete(session, task, error);
? ? }
}
??:AFN中使用了兩個(gè)操作隊(duì)列,一條是在AFURLSessionManager里的初始化方法里創(chuàng)建的最大并發(fā)數(shù)為1的NSOperationQueue肮柜,用來處理所有的網(wǎng)絡(luò)請(qǐng)求和等待響應(yīng)陷舅。數(shù)據(jù)的解析在一個(gè)異步并發(fā)的操作隊(duì)列里執(zhí)行。
同時(shí)多個(gè)網(wǎng)絡(luò)請(qǐng)求直接多次調(diào)用AFHTTPSessionManager的GET方法就行了审洞。一個(gè)請(qǐng)求依賴另一個(gè)請(qǐng)求的結(jié)果莱睁,在第一個(gè)請(qǐng)求的成功或失敗回調(diào)中發(fā)起第二個(gè)請(qǐng)求就是最好的方法。
參考文章:AFNetworking源碼分析
AFN中的兩種代理:
有三個(gè)代理方法轉(zhuǎn)發(fā)到了AFN的代理中芒澜,這三個(gè)方法里的代理是需要對(duì)應(yīng)每個(gè)task做私有化處理的仰剿。
其他的方法都試針對(duì)這個(gè)sessionManager所有的request的,是公用的處理痴晦。
三個(gè)方法分別是:didCompleteWithError南吮、didReceiveData、didFinishDownloadingToURL
6.AFN中圖片的解壓
首先我們要知道為什么要對(duì)圖片解壓誊酌,我們下載到的JPG部凑,PNG類型的圖片是不能直接用來顯示的露乏。當(dāng)我們調(diào)用UIImage的方法imageWithData:方法把數(shù)據(jù)轉(zhuǎn)成UIImage對(duì)象后,其實(shí)這時(shí)UIImage對(duì)象還沒準(zhǔn)備好需要渲染到屏幕的數(shù)據(jù)涂邀,現(xiàn)在的網(wǎng)絡(luò)圖像PNG和JPG都是壓縮格式瘟仿,需要把它們解壓轉(zhuǎn)成bitmap后才能渲染到屏幕上,如果不做任何處理比勉,當(dāng)你把UIImage賦給UIImageView劳较,在渲染之前底層會(huì)判斷到UIImage對(duì)象未解壓,沒有bitmap數(shù)據(jù)敷搪,這時(shí)會(huì)在主線程對(duì)圖片進(jìn)行解壓操作兴想,再渲染到屏幕上幢哨。這個(gè)解壓操作是比較耗時(shí)的喉脖,如果任由它在主線程做慕购,可能會(huì)導(dǎo)致速度慢UI卡頓的問題。
7.網(wǎng)絡(luò)請(qǐng)求中的緩存
網(wǎng)絡(luò)請(qǐng)求的緩存和我們經(jīng)常用到的圖片的緩存,數(shù)據(jù)的緩存其實(shí)是一個(gè)木目的哥艇,就是為了加快請(qǐng)求的響應(yīng)時(shí)間,避免重復(fù)發(fā)送相同的請(qǐng)求旗吁,同時(shí)提升離線或低網(wǎng)速情況下的用戶體驗(yàn)翰灾。
當(dāng)一個(gè)請(qǐng)求完成下載來自服務(wù)器的回應(yīng),一個(gè)緩存的回應(yīng)將在本地保存凸丸。下一次同一個(gè)請(qǐng)求再發(fā)起時(shí)拷邢,本地保存的回應(yīng)就會(huì)馬上返回,不需要連接服務(wù)器屎慢。
AFN中的緩存機(jī)制是通過NSCache來實(shí)現(xiàn)的瞭稼。