對比iOS網絡組件:AFNetworking VS ASIHTTPRequest

在開發iOS應用過程中,如何高效的與服務端API進行數據交換,是一個常見問題。一般開發者都會選擇一個第三方的網絡組件作爲服務,以提高開發效率和穩定性。這些組件把複雜的網絡底層操作封裝成友好的類和方法,並且加入異常處理等。

那麼,大家最常用的組件是什麼?這些組件是如何提升開發效率和穩定性的?哪一款組件適合自己,是 AFNetworking(AFN)還是 ASIHTTPRequest(ASI)?幾乎每一個iOS互聯網應用開發者都會面對這樣的選擇題,要從這兩個最常用的組件裏選出一個好的還真不是那麼容易。

單單從兩個控件版本提交的時間節點來看,AFN的第一個提交是2011年的1月1日,那個時候ASI早已是1.8+的版本了;而當AFN發佈1.0版,2012年10月份的時候,ASI早早的已經停止更新了。這樣看起來,AFN是ASI的繼任者,似乎不存在之前提到的選擇困難的問題,而事實並非如此。本文將從用法、功能、性能和原理幾個方面對二者進行簡單對比,看看二者之間到底存在着怎樣的區別,到底應該如何選擇。

1、用法對比

首先,從推薦用法上就可以看出二者設計理念上大有不同。

圖1,AFN的示例代碼,發起請求

圖1,AFN的示例代碼,發起請求(出自:Posts.m

AFN官方推薦的使用方法是,爲一系列相關的請求定義一個HTTPClient,共用一個BaseURL。每次請求把URL中除BaseURL的Path部分做爲參數傳給HTTPClient的靜態方法,並註冊一個Block用於回調。

圖2,ASI示例代碼,發起異步請求

圖2,ASI示例代碼,發起異步請求(出自:ASIHTTPRequestTests.m

ASI推薦使用方法就非常傳統,每一個請求都由構造方法初始化一個(共享)實例,通過這個實例配置參數併發起請求。ASI最初使用delegate模式回調,在iOS SDK支持Block之後也提供了註冊Block的實例方法。

以上引用的兩段代碼都出自各自項目的示例工程。對比兩段代碼可以很清楚的看出,同樣是發起一個最普通的異步請求,使用AFN只需要調用一個靜態方法,但代碼可讀性較差;而ASI的示例看起來更清晰,但需要調用多個實例方法才能完成一次請求。AFN的設計更加工程化,或者說對使用者更友好,而ASI的設計更經典,典型的OOP。

除了初級用法上的區別,二者的高級功能和對擴展的支持也頗有不同。

2、高級功能

AFN只封裝了一些常用功能,滿足基本需求,而直接忽略了很多擴展功能。例如:AFN默認沒有封裝同步請求,如果開發者需要使用同步請求,則需要重寫getPath:parameters:success:failure方法,對AFHTTPRequestOperation進行同步處理;而ASI則是直接通過調用一個startSynchronous方法。

此外AFN針對JSONXMLPListImage四種數據結構封裝了各自處理器,開發者可以把處理器註冊到操作隊列中,直接在回調方法中獲得格式化以後的數據。在示例工程中就使用了JSON處理器:把AFJSONRequestOperation註冊到操作隊列裏。

圖3,AFN示例代碼,初始化自定義的HTTPClient

圖3,AFN示例代碼,初始化自定義的HTTPClient(出自:AFAppDotNetAPIClient.m

而ASI在這方面顯得更原始,沒有針對任何數據類型做特別封裝,只是預留了各種接口和工具供開發者自行擴展。ASI比AFN提供更多擴展功能還有一個原因,它把許多內部用到的功能也抽象成類和方法。例如:

ASIHTTPRequestDataCompressor和ASIHTTPRequestDataDecompressor兩個類,只用於壓縮本地文件,構造POST Body和解壓縮返回數據,但這兩個類仍然被設計爲獨立功能,提供了對多種數據結構進行壓縮和解壓縮的方法。

對比二者的高級功能和對擴展的支持後,可以看出AFN把初級功能(或者叫常用功能)做到了90分。調用方式夠簡單,處理器夠豐富,使用者用起來可以算是輕鬆加愉快。但它放棄了對高級功能的支持,要滿足較複雜的需求,就要大費周折了,在這方面最多隻有40分。而ASI顯然不滿足於做好初級功能,但爲了提供更豐富的可擴展接口,導致初級功能用起來也要花上一些力氣。雖然ASI單獨提供了支持Amazon S3Rackspace Cloud Files的控件,但對於生在紅旗下的我朝開發者來說基本沒用,所以在初級功能的支持上ASI能得個70分,犧牲了初級功能的易用性,換來的是良好的擴展性,在高級功能的使用上遠遠好於AFN,也能得個70分。

從使用角度對比過後,基本上對這兩個項目有一個整體上的認識,再深入下去看看二者的性能如何。

3、性能對比

我分別用AFN和ASI進行了測試,測試環境如下:iPhone5,聯通3G信號全滿,室內靜止狀態,請求國內雙線機房獨立服務器的靜態文件,1~20K共20個文件,每個文件請求20次,記錄從創建請求到完全下載文件的耗時,結果如下:

圖4,AFN連續訪問1 ~ 20K文件耗時

圖4,AFN連續訪問1 ~ 20K文件耗時

圖5,ASI連續訪問1 ~ 20K文件耗時

圖5,ASI連續訪問1 ~ 20K文件耗時

圖4是AFN的記錄圖,綠色爲20次請求中耗時最久的一次,藍色爲耗時最短的一次,黃色爲去除最大值和最小值的18次平均值。從這個圖可以看出,AFN最開始創建對象耗時近2.5秒,隨後穩定下來,在3K、7K、15K和20K時出現了抖動。圖5是ASI做相同測試的結果,首次創建對象近2.25秒,略優於AFN,同樣在5K、11K、13K、14K和16K發生了一些抖動,但抖動幅度似乎小於AFN,可見穩定性更好一些。

下邊是把二者的測試結果放在一起的對比圖,可以更直觀的比較二者的區別。

圖6,ASI和AFN耗時最大值對比

圖6,ASI和AFN耗時最大值對比

圖6的最大值對比可以更明顯的看出二者的抖動對比,ASI略好一些。

圖7,ASI和AFN耗時最小值對比

圖7,ASI和AFN耗時最小值對比

圖7的最小值對比可以看出,在每一個大小的測試中ASI的最佳性能似乎都要優於AFN。

圖8,ASI和AFN耗時平均值對比

圖8,ASI和AFN耗時平均值對比

圖8是耗時平均值的對比,更能夠說明問題。文件小於12K的測試中ASI的性能優勢並沒有非常明顯,超過12K以後,ASI優勢開始明顯起來,每一次請求都要比AFN節約20% ~ 30%,近0.1秒。同時從這張圖上還可以看出,隨着下載文件變大,請求耗時並不是線形增長的,這是由於一次請求大部分時間都消耗在建立連接上,而真正接收數據只佔用了極少時間,這個問題不在本篇文章的討論範圍,所以不多說,有興趣的讀者可以移步http://segmentfault.com/t/ios進一步討論。

4、原理分析

ASI的性能似乎全面優於AFN,那下邊從二者的實現原理上看一下到底是什麼原因造成這種差距。ASI基於CFNetwork框架開發,而AFN基於NSURL,底層的區別是導致二者性能差距的重要原因之一。

圖9,ASI和AFN以及底層框架的關係

圖9,ASI和AFN以及底層框架的關係

我們知道所有網絡通信的基礎是Socket,一個Socket與另一個連接並傳送數據。BSD Socket是一類最常見的Socket抽象接口。

Core Foundation框架中的CFSocket就是基於BSD Socket開發的。它幾乎涵蓋了BSD Socket的全部功能,更重要的是把Socket整合到事件的處理循環中。Core Founda-tion中較高層的CFStream是基於CFSocket開發的讀寫流支持。

CFNetwork是基於Core Foundation中CFStream的一個底層高性能網絡框架,它由提供基礎服務的CFSocketStream,支持HTTP協議的CFHTTP,基於CFHTTP用於身份認證的CFHTTPAuthentication和支持FTP協議的CFFTP組成。

正如圖9所示,ASI是基於CFHTTP開發的一個組件;而AFN的基礎——NSURL,也是基於CFNetwork開發的。也就是說ASI相比AFN更加底層,這就從一定程度上造成二者的性能差距。

另一個方面,雖然二者都使用NSOperation和NSOperationQueue實現但底層的區別也導致實現方式上有非常大的差別。

ASI的直接操作對象ASIHTTPRequest是NSOperation的子類,實現了NSCopying協議。在initialize和initWithURL:方法中初始化相關屬性並配置一系列請求相關參數默認值。此外,ASIHTTPRequest還提供了一系列的實例方法用來配置請求對象。在異步請求的處理上,ASIHTTPRequest對象初始化結束後,在startAsynchronous方法中把對象加入共享操作隊列。此後,包括創建CFHTTPMessageRef,也就是處理網絡請求的主要對象(事實上是一個指向__CFHTTPMessage結構的指針),在內的所有操作都在ASIHTTPRequest對象所屬的子線程中完成。

AFN的直接操作對象AFHTTPClient不同於ASI,是一個實現了NSCoding和NSCopying協議的NSObject子類。AFHTTPClient是一個封裝了一系列操作方法的“工具類”,處理請求的操作類是一系列單獨的,基於NSOperation封裝的,AFURLConnectionOperation的子類。AFN的示例代碼中通過一個靜態方法,使用dispatch_once()的方式創建AFHTTPClient的共享實例,這也是官方建議的使用方法。在創建AFHTTPClient的初始化方法中,創建了OperationQueue並設置一系列參數默認值。在getPath:parameters:success:failure方法中創建NSURLRequest,以NSURLRequest對象實例作爲參數,創建一個NSOperation,並加入在初始化發方中創建的NSOperationQueue。以上操作都是在主線程中完成的。在NSOperation的start方法中,以此前創建的NSURLRequest對象爲參數創建NSURLConnection並開啓連結。

在異步回調的處理上二者也有區別,ASI採取的是CFHTTP請求完成,直接回調ASIHTTPRequest的實例方法,通過儲存的實例對象記錄的信息完成Delegate模式或Block模式的回調。而AFN則直接使用了NSOperation的completionBlock屬性。

這些實現方式也可以看出,ASI顯得更加底層,並沒有過多使用Cocoa框架中已經封裝的API,而AFN則更加實用主義,邏輯簡單清晰,大量使用了框架API。這一點也是造成二者性能差別的原因之一。

總結

通過以上的對比,基本可以這樣評價:AFN適合邏輯簡單的應用,或者更適合開發資源尚不豐富的團隊,因爲AFN的易用性要比ASI好很多,而這樣的應用(或團隊)對底層網絡控件的定製化要求也非常低。ASI更適合已經發展了一段時間的應用,或者開發資源相對豐富的團隊,因爲往往這些團隊(或他們的應用)已經積累了一定的經驗,無論是產品上還是技術上的。需求複雜度就是在這種時候高起來,而且底層訂製的需求也越來越多,此時AFN就很難滿足需求,需要犧牲一定的易用性,使用ASI作爲網絡底層控件。SegmentFault開源客戶端現在被設計爲一款簡單的閱讀客戶端,幾乎沒有定製要求,因此,目前我選擇了AFN作爲網絡控件。

以上對ASI和AFN兩款最常用的iOS底層網絡控件做了初步的介紹,要更深入的瞭解兩款控件,還需要大家繼續研究各自的源碼。大家遇到任何關於iOS的技術問題都可以在http://segmentfault.com/t/ios進行討論。另外大家也可以持續關注SegmentFault的開源客戶端,與更多的開發者共同探討iOS開發技術。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章