在開發(fā)iOS應用過程中,,如何高效的與服務端API進行數(shù)據(jù)交換,是一個常見問題,。一般開發(fā)者都會選擇一個第三方的網(wǎng)絡組件作為服務,,以提高開發(fā)效率和穩(wěn)定性。這些組件把復雜的網(wǎng)絡底層操作封裝成友好的類和方法,,并且加入異常處理等,。 那么,大家最常用的組件是什么,?這些組件是如何提升開發(fā)效率和穩(wěn)定性的,?哪一款組件適合自己,是 AFNetworking(AFN)還是 ASIHTTPRequest(ASI),?幾乎每一個iOS互聯(lián)網(wǎng)應用開發(fā)者都會面對這樣的選擇題,,要從這兩個最常用的組件里選出一個好的還真不是那么容易。 單單從兩個控件版本提交的時間節(jié)點來看,,AFN的第一個提交是2011年的1月1日,,那個時候ASI早已是1.8+的版本了;而當AFN發(fā)布1.0版,,2012年10月份的時候,,ASI早早的已經(jīng)停止更新了。這樣看起來,,AFN是ASI的繼任者,,似乎不存在之前提到的選擇困難的問題,而事實并非如此,。本文將從用法,、功能,、性能和原理幾個方面對二者進行簡單對比,看看二者之間到底存在著怎樣的區(qū)別,,到底應該如何選擇,。 1、用法對比首先,,從推薦用法上就可以看出二者設計理念上大有不同,。 圖1,,AFN的示例代碼,發(fā)起請求(出自:Posts.m) AFN官方推薦的使用方法是,,為一系列相關的請求定義一個HTTPClient,,共用一個BaseURL。每次請求把URL中除BaseURL的Path部分做為參數(shù)傳給HTTPClient的靜態(tài)方法,,并注冊一個Block用于回調(diào),。 圖2,,ASI示例代碼,發(fā)起異步請求(出自:ASIHTTPRequestTests.m) ASI推薦使用方法就非常傳統(tǒng),,每一個請求都由構(gòu)造方法初始化一個(共享)實例,,通過這個實例配置參數(shù)并發(fā)起請求。ASI最初使用delegate模式回調(diào),,在iOS SDK支持Block之后也提供了注冊Block的實例方法,。 以上引用的兩段代碼都出自各自項目的示例工程。對比兩段代碼可以很清楚的看出,,同樣是發(fā)起一個最普通的異步請求,,使用AFN只需要調(diào)用一個靜態(tài)方法,但代碼可讀性較差,;而ASI的示例看起來更清晰,,但需要調(diào)用多個實例方法才能完成一次請求。AFN的設計更加工程化,,或者說對使用者更友好,,而ASI的設計更經(jīng)典,典型的OOP,。 除了初級用法上的區(qū)別,二者的高級功能和對擴展的支持也頗有不同,。 2,、高級功能AFN只封裝了一些常用功能,,滿足基本需求,而直接忽略了很多擴展功能,。例如:AFN默認沒有封裝同步請求,,如果開發(fā)者需要使用同步請求,則需要重寫getPath:parameters:success:failure方法,,對AFHTTPRequestOperation進行同步處理,;而ASI則是直接通過調(diào)用一個startSynchronous方法。 此外AFN針對JSON,、XML,、PList和Image四種數(shù)據(jù)結(jié)構(gòu)封裝了各自處理器,開發(fā)者可以把處理器注冊到操作隊列中,,直接在回調(diào)方法中獲得格式化以后的數(shù)據(jù),。在示例工程中就使用了JSON處理器:把AFJSONRequestOperation注冊到操作隊列里。 圖3,AFN示例代碼,,初始化自定義的HTTPClient(出自:AFAppDotNetAPIClient.m) 而ASI在這方面顯得更原始,,沒有針對任何數(shù)據(jù)類型做特別封裝,只是預留了各種接口和工具供開發(fā)者自行擴展,。ASI比AFN提供更多擴展功能還有一個原因,,它把許多內(nèi)部用到的功能也抽象成類和方法。例如: ASIHTTPRequestDataCompressor和ASIHTTPRequestDataDecompressor兩個類,,只用于壓縮本地文件,,構(gòu)造POST Body和解壓縮返回數(shù)據(jù),但這兩個類仍然被設計為獨立功能,,提供了對多種數(shù)據(jù)結(jié)構(gòu)進行壓縮和解壓縮的方法,。 對比二者的高級功能和對擴展的支持后,可以看出AFN把初級功能(或者叫常用功能)做到了90分,。調(diào)用方式夠簡單,,處理器夠豐富,使用者用起來可以算是輕松加愉快,。但它放棄了對高級功能的支持,,要滿足較復雜的需求,就要大費周折了,,在這方面最多只有40分,。而ASI顯然不滿足于做好初級功能,但為了提供更豐富的可擴展接口,,導致初級功能用起來也要花上一些力氣,。雖然ASI單獨提供了支持Amazon S3和Rackspace Cloud Files的控件,,但對于生在紅旗下的我朝開發(fā)者來說基本沒用,所以在初級功能的支持上ASI能得個70分,,犧牲了初級功能的易用性,,換來的是良好的擴展性,在高級功能的使用上遠遠好于AFN,,也能得個70分,。 從使用角度對比過后,基本上對這兩個項目有一個整體上的認識,,再深入下去看看二者的性能如何,。 3、性能對比我分別用AFN和ASI進行了測試,,測試環(huán)境如下:iPhone5,,聯(lián)通3G信號全滿,室內(nèi)靜止狀態(tài),,請求國內(nèi)雙線機房獨立服務器的靜態(tài)文件,,1~20K共20個文件,每個文件請求20次,,記錄從創(chuàng)建請求到完全下載文件的耗時,,結(jié)果如下: 圖4,,AFN連續(xù)訪問1 ~ 20K文件耗時 圖5,ASI連續(xù)訪問1 ~ 20K文件耗時 圖4是AFN的記錄圖,,綠色為20次請求中耗時最久的一次,,藍色為耗時最短的一次,黃色為去除最大值和最小值的18次平均值,。從這個圖可以看出,,AFN最開始創(chuàng)建對象耗時近2.5秒,隨后穩(wěn)定下來,,在3K,、7K、15K和20K時出現(xiàn)了抖動,。圖5是ASI做相同測試的結(jié)果,,首次創(chuàng)建對象近2.25秒,略優(yōu)于AFN,,同樣在5K,、11K、13K,、14K和16K發(fā)生了一些抖動,,但抖動幅度似乎小于AFN,,可見穩(wěn)定性更好一些。 下邊是把二者的測試結(jié)果放在一起的對比圖,,可以更直觀的比較二者的區(qū)別。 圖6,,ASI和AFN耗時最大值對比 圖6的最大值對比可以更明顯的看出二者的抖動對比,ASI略好一些,。 圖7,ASI和AFN耗時最小值對比 圖7的最小值對比可以看出,,在每一個大小的測試中ASI的最佳性能似乎都要優(yōu)于AFN,。 圖8,,ASI和AFN耗時平均值對比 圖8是耗時平均值的對比,,更能夠說明問題。文件小于12K的測試中ASI的性能優(yōu)勢并沒有非常明顯,,超過12K以后,,ASI優(yōu)勢開始明顯起來,每一次請求都要比AFN節(jié)約20% ~ 30%,,近0.1秒,。同時從這張圖上還可以看出,隨著下載文件變大,,請求耗時并不是線形增長的,,這是由于一次請求大部分時間都消耗在建立連接上,而真正接收數(shù)據(jù)只占用了極少時間,,這個問題不在本篇文章的討論范圍,,所以不多說,有興趣的讀者可以移步http://segmentfault.com/t/ios進一步討論,。 4,、原理分析ASI的性能似乎全面優(yōu)于AFN,那下邊從二者的實現(xiàn)原理上看一下到底是什么原因造成這種差距,。ASI基于CFNetwork框架開發(fā),,而AFN基于NSURL,底層的區(qū)別是導致二者性能差距的重要原因之一,。 圖9,ASI和AFN以及底層框架的關系 我們知道所有網(wǎng)絡通信的基礎是Socket,,一個Socket與另一個連接并傳送數(shù)據(jù),。BSD Socket是一類最常見的Socket抽象接口,。 Core Foundation框架中的CFSocket就是基于BSD Socket開發(fā)的。它幾乎涵蓋了BSD Socket的全部功能,,更重要的是把Socket整合到事件的處理循環(huán)中,。Core Founda-tion中較高層的CFStream是基于CFSocket開發(fā)的讀寫流支持。 CFNetwork是基于Core Foundation中CFStream的一個底層高性能網(wǎng)絡框架,,它由提供基礎服務的CFSocketStream,,支持HTTP協(xié)議的CFHTTP,基于CFHTTP用于身份認證的CFHTTPAuthentication和支持FTP協(xié)議的CFFTP組成,。 正如圖9所示,,ASI是基于CFHTTP開發(fā)的一個組件;而AFN的基礎——NSURL,,也是基于CFNetwork開發(fā)的,。也就是說ASI相比AFN更加底層,這就從一定程度上造成二者的性能差距,。 另一個方面,,雖然二者都使用NSOperation和NSOperationQueue實現(xiàn)但底層的區(qū)別也導致實現(xiàn)方式上有非常大的差別。 ASI的直接操作對象ASIHTTPRequest是NSOperation的子類,,實現(xiàn)了NSCopying協(xié)議,。在initialize和initWithURL:方法中初始化相關屬性并配置一系列請求相關參數(shù)默認值。此外,,ASIHTTPRequest還提供了一系列的實例方法用來配置請求對象,。在異步請求的處理上,ASIHTTPRequest對象初始化結(jié)束后,,在startAsynchronous方法中把對象加入共享操作隊列,。此后,包括創(chuàng)建CFHTTPMessageRef,,也就是處理網(wǎng)絡請求的主要對象(事實上是一個指向__CFHTTPMessage結(jié)構(gòu)的指針),,在內(nèi)的所有操作都在ASIHTTPRequest對象所屬的子線程中完成。 AFN的直接操作對象AFHTTPClient不同于ASI,,是一個實現(xiàn)了NSCoding和NSCopying協(xié)議的NSObject子類,。AFHTTPClient是一個封裝了一系列操作方法的“工具類”,處理請求的操作類是一系列單獨的,,基于NSOperation封裝的,,AFURLConnectionOperation的子類。AFN的示例代碼中通過一個靜態(tài)方法,,使用dispatch_once()的方式創(chuàng)建AFHTTPClient的共享實例,,這也是官方建議的使用方法。在創(chuàng)建AFHTTPClient的初始化方法中,創(chuàng)建了OperationQueue并設置一系列參數(shù)默認值,。在getPath:parameters:success:failure方法中創(chuàng)建NSURLRequest,,以NSURLRequest對象實例作為參數(shù),創(chuàng)建一個NSOperation,,并加入在初始化發(fā)方中創(chuàng)建的NSOperationQueue,。以上操作都是在主線程中完成的。在NSOperation的start方法中,,以此前創(chuàng)建的NSURLRequest對象為參數(shù)創(chuàng)建NSURLConnection并開啟連結(jié),。 在異步回調(diào)的處理上二者也有區(qū)別,ASI采取的是CFHTTP請求完成,,直接回調(diào)ASIHTTPRequest的實例方法,通過儲存的實例對象記錄的信息完成Delegate模式或Block模式的回調(diào),。而AFN則直接使用了NSOperation的completionBlock屬性,。 這些實現(xiàn)方式也可以看出,ASI顯得更加底層,,并沒有過多使用Cocoa框架中已經(jīng)封裝的API,,而AFN則更加實用主義,邏輯簡單清晰,,大量使用了框架API,。這一點也是造成二者性能差別的原因之一。 總結(jié)通過以上的對比,,基本可以這樣評價:AFN適合邏輯簡單的應用,,或者更適合開發(fā)資源尚不豐富的團隊,因為AFN的易用性要比ASI好很多,,而這樣的應用(或團隊)對底層網(wǎng)絡控件的定制化要求也非常低,。ASI更適合已經(jīng)發(fā)展了一段時間的應用,或者開發(fā)資源相對豐富的團隊,,因為往往這些團隊(或他們的應用)已經(jīng)積累了一定的經(jīng)驗,,無論是產(chǎn)品上還是技術上的。需求復雜度就是在這種時候高起來,,而且底層訂制的需求也越來越多,,此時AFN就很難滿足需求,需要犧牲一定的易用性,,使用ASI作為網(wǎng)絡底層控件,。SegmentFault開源客戶端現(xiàn)在被設計為一款簡單的閱讀客戶端,幾乎沒有定制要求,,因此,,目前我選擇了AFN作為網(wǎng)絡控件。 以上對ASI和AFN兩款最常用的iOS底層網(wǎng)絡控件做了初步的介紹,要更深入的了解兩款控件,,還需要大家繼續(xù)研究各自的源碼,。大家遇到任何關于iOS的技術問題都可以在http://segmentfault.com/t/ios進行討論。另外大家也可以持續(xù)關注SegmentFault的開源客戶端,,與更多的開發(fā)者共同探討iOS開發(fā)技術,。 |
|