ASIHTTPRequest (做者:BenCopsey) 是一個使用簡單,可用於各類從簡單到複雜的 HTTP 請求,或者可用於處理 Amazon S三、Rackspace 等REST 服務的強大框架。ios
不幸的是,Ben 早在 2011 年 9 月 21 日就已經聲明中止開發和支持該框架(見http://allseeing-i.com/%5Brequest_release%5D; )。數組
Ben 推薦了許多可替代的框架(好比AFNetworking, RestKit 或 LRResty)。但最有潛力的莫過於Mugunth Kumar 的 MKNetworkKit 。Mugunth 曾發佈了許多高質量的開源的 iOS/Mac 代碼(好比 MKStoreKit),其中值得推薦一個就是 ASHTTPRequest 的替代者: MKNetworkKit。它支持 ARC 和塊,易於使用且極爲高效。緩存
如下內容摘自 Mugunth本身的博客。原文位於:http://blog.mugunthkumar.com/products/ios-framework-introducing-mknetworkkit。服務器
假設有一個網絡框架,它能自動爲你緩存 respones,能在你離線時自動記憶你的操做,你以爲怎樣?網絡
當你離線時,你能夠收藏某個 tweet 頁或者標記某個 feed 爲已讀,當你再次上線時,網絡框架會自動執行你的這些操做,這一切都不須要你額外編寫代碼。請看我對於MKNetworkKit 框架的介紹。併發
什麼是MKNetworkKit?app
MKNetworkKit是一個 O-C 編寫的網絡框架,支持塊,ARC 且用法簡單。框架
MKNetworkKit 集 ASIHTTPRequest 和 AFNetworking 兩個框架於一體。在集成兩者的優秀特性以外,還增長了一堆新的功能。尤爲是,相比起其它框架,它能讓你更輕鬆地編寫代碼。它讓你完全遠離那些噁心的網絡代碼。異步
特色ide
超輕量級框架
整個框架只有 2 個類和一些類別方法。所以,它的使用極其簡單。
在整個程序中只有一個全局隊列
高度依賴互聯網鏈接的 app 應該優先考慮網絡線程的併發數。不幸的是,沒有任何網絡框架在這方面作得夠好。所以,一旦你在程序中沒有控制好網絡線程的併發數,就極易致使出錯。
假設你要上傳一堆圖片到服務器上。絕大多數移動網絡(3G)不會容許你對同一個IP 地址的 HTTP 併發鏈接數超過 2 個。換句話說,在設備上,你不能從 3G 網絡中得到 2 個以上的 HTTP 併發鏈接。對於 Edge 則更糟,大多數狀況不能超過1 個。相比較家用寬帶網絡(Wifi),則這個限制要寬得多(6 個)。可是,你不可能老是使用 wifi,你必須也考慮到有限網絡(窄帶)的連通性。更多的時候,iDevice設備幾乎都能鏈接到 3G 網絡,所以,你同時只能上傳 2 張圖片。可是,真正的問題不是緩慢的上傳速度,而是另外一種狀況。在你打開一個 view 試圖加載縮略圖(不一樣的view)時,上傳線程被運行到後臺。若是你沒有控制好上傳隊列中的線程數,你的縮略圖會加載超時。這是不正常的。正確的方式是優化縮略圖加載線程,或者讓線程等待直到上傳完成再加載縮略圖。這須要你在整個程序中只擁有一個queue 隊列。
MKNetworkKit 在它的每一個實例中使用單例來保證這一點。並非說MKNetworkKit 是單例的,而是說它的共享隊列是單例的。
正確顯示網絡狀態指示
許多第 3 方框架都經過一個「網絡鏈接數增長/減小」的方法回調來顯示網絡狀態,MKNetworkKit則因爲使用了單例的共享隊列,能自動顯示網絡狀態。在共享隊列中有一個線程經過 KVO 方式會隨時觀察 operationCount 屬性。所以對於開發者,通常狀況下根本不須要操心網絡狀態的顯示。
if (object == _sharedNetworkQueue && [keyPath isEqualToString:@"operationCount"]) { [UIApplication sharedApplication].networkActivityIndicatorVisible = ([_sharedNetworkQueue.operations count] < 0); } |
自動改變隊列大小
如前所述,絕大部分移動網絡不容許 2 個以上的併發鏈接,所以你的隊列大小在3G 網絡下應當設置爲 2。 MKNetworkKit 會自動爲你處理好這個。當網絡出於3G/EDGE/GPRS 時,它會將併發數調整到 2。當網絡處於 Wifi 網絡時,則自動調整到 6。當你經過 3G 網絡中從遠程服務器加載縮略圖時,這種調整能帶來極大的好處。
自動緩存
MKNetworkKit 能自動緩存你全部的 GET 請求。當你再次發起一樣的請求時,MKNetworkKit 隨即就能調用 response緩存(若是可用的話)傳遞給 handler 進行處理。固然,它同時也向服務器發出請求。一旦得到服務器數據,handler 被再次要求處理新獲取的數據。也就是說,你不用手動緩存。你只須要使用:
[[MKNetworkEngine sharedEngine] useCache]; |
固然,你能夠覆蓋這個方法(子類化),定製你的緩存路徑和緩存佔用的內存開銷。
凍結網絡操做
MKNetworkKit 可以「凍結」網絡操做。在一個網絡操做被「凍結」的狀況下,一旦網絡連斷開,它們將自動序列化並在設備再次連線時自動被提交一次。相似 twitter 客戶端的「drafts」。
當你提交一篇 tweet 時,若是網絡被標記爲「可凍結」,MKNetworkKit 會自動執行凍結並儲存這些請求。所以會在未來推遲發送這篇 tweet。整個過程不須要你寫一行代碼。這個特性你能夠用於其餘操做,諸如收藏一篇 tweet 或者從 Goolge reader 客戶端共享一個帖子,加一個連接到Instapaper 中,等等。
相似的請求只執行一個操做
當你加載縮略圖(針對 twitter stream)時,你最終得爲每一個實際的圖片建立一個新的請求。實際上你所進行的多個請求都是同一個URL。MKNetworkKit 對於隊列中的每一個 GET 請求都只會執行一次。它還不能到緩存 POST 請求。
圖片緩存
MKNetworkKit 內置了縮略圖緩存。只要覆蓋幾個方法,就能夠設置內存中最大能緩存的圖片數量,以及緩存要保存到目錄。固然,你也能夠不覆蓋這些方法。
性能
即速度。MKNetworkKit 緩存是內置的,就如 NSCache,當發現有內存警告,緩存到內存中的數據將被寫入緩存目錄。
徹底支持 ARC
通常你只會在新項目中使用新的網絡框架。MKNetworkKit並不意味着要放棄已有的框架(固然你也能夠放棄,這會是個乏味的工做)。對於新的項目,你老是想使用 ARC。當你看到本文的時候,極可能 MKNetworkKit 會是僅有的徹底支持 ARC 的網絡框架。ARC 一般比非 ARC 代碼更快。
用法
Ok,我就不「自賣自詡」了。讓咱們當即瞭解若是使用這個框架。
添加MKNetworkKit
將 MKNetworkKit 目錄拖到項目中
添加下列框架: CFNetwork.Framework, SystemConfiguration.framework, Security.framework and ImageIO.Framework.
將 MKNetworkKit.h 頭文件包含到 PCH 文件中
對於 iOS,刪除 NSAlert+MKNetworkKitAdditions.h
對於 Mac,刪除 UIAlertView+MKNetworkKitAdditions.h
總共只須要 5 個核心文件,真是一個強大的網絡開發包
MKNetworkKit 的類
MKNetworkOperation
MKNetworkEngine
一些工具類 (Apple 的 Reachability) 以及類別
我喜歡簡單。蘋果已經寫了最基本最核心的網絡代碼。第 3 方框架須要的是提供一個優雅的網絡隊列最多再加上緩存。我認爲第3 方框架不該該超過 10 個類(不管它是網絡的仍是 UIKit 仍是別的什麼)。超過這個數就太臃腫了。Three20 就是一個例子。如今 ShareKit 又是這樣。儘管它們是優秀的,但仍然是龐大和臃腫的。ASIHttpRequest or AFNetworking 比 RESTKit 更輕,JSONKit比TouchJSON (或者任何 TouchCode 庫)更輕。這只是我本身的見解,但當一個第三方庫的代碼超過程序源代碼1/3,我就不會使用它。
框架臃腫帶來的問題是很難理解它的內部工做機制,以及很難根據本身的需求定製它(當你須要時)。我曾經寫過的一些框架(例如MKStoreKit ,用於應用程序內購的 )老是易於使用,我認爲MKNetworkKit 也應該是這樣。對於 MKNetworkKit ,你所須要瞭解的就是暴露在兩個類MKNetworkOperation 和 MKNetworkEngine 中的方法。MKNetworkOperation 就比如ASIHttpRequest類。它是一個NSOperation 子類,封裝了你的 request 和 response 類。對於每一個網絡操做,你須要建立一個MKNetworkOperation 。
MKNetworkEngine 是一個僞單例類,管理程序中的網絡隊列。它是僞單例的,也就是說,對於簡單請求,你能夠直接用MKNetworkEngine 中的方法。要進行深度的定製,你應該進行子類化。每一個 MKNetworkEngine 子類有它本身的Reachability 對象,用於通知它來自服務器的reachability 通知。對於不一樣的 REST 服務器,你能夠考慮建立單獨的 MKNetworkEngine子類。
它是僞單例,它的子類的每一個請求都共用惟一的一個隊列。你能夠在應用程序委託中retain 這個 MKNetworkEngine ,就像CoreData 的 managedObjectContext 類同樣。在使用MKNetworkKit 時,建立一個 MKNetworkEngine 子類將你的網絡請求進行邏輯上的分組。例如,將全部關於 Yahoo 的方法放在一個類,全部 Facebook 有關的方法放進另外一個類。來看 3 個實際使用的例子。
例1:
建立一個 「YahooEngine」 從 Yahoo 財經服務器抓取貨幣匯率。
步驟 1:建立YahooEngine 類繼承於MKNetworkEngine。MKNetworkEngine 使用主機名和指定的頭(若是有的話)進行初始化。頭信息能夠是nil。若是你是在本身的 REST 服務器上,你能夠考慮加一個客戶端 app 的版本或者其餘信息(好比客戶端的標識)。
NSMutableDictionary *headerFields = [NSMutableDictionary dictionary]; [headerFields setValue:@"iOS"forKey:@"x-client-identifier"]; self.engine = [[YahooEngine alloc] initWithHostName:@"download.finance.yahoo.com" customHeaderFields:headerFields]; |
注意,yahoo 並不識別你在頭中發送x-client-identifier 給它,這個示例僅僅是演示這個特性而
因爲使用了 ARC 代碼,做爲開發者你須要擁有(強引用)Engine對象。
一旦你建立了一個 MKNetworkEngine子類, Reachability 即自動實現。當你的服務器因爲某些狀況掛了,主機名不可訪問,你的請求會自動被凍結。關於「凍結」,請參考後面的「凍結操做」小節。
步驟 2:設計Engine 類 (關注分離)
如今,開始編寫 Yahoo Engine 中的方法,以抓取匯率。這些方法將在ViewController 中被調用。良好的設計體驗是確保不要將 engine 類中的 URL/HTTPHeaders 暴露給調用者。你的視圖不該該知道URL 或者相關的參數。也就是,只須要向 engine 方法傳遞貨幣種類和貨幣單位就能夠了。方法的返回值多是 double,即匯率,以及獲取匯率的時間。因爲是異步操做,你應當在塊中返回這些值。例如:
-(MKNetworkOperation*) currencyRateFor:(NSString*) sourceCurrency inCurrency:(NSString*) targetCurrency onCompletion:(CurrencyResponseBlock) completion onError:(ErrorBlock) error; |
在父類 MKNetworkEngine 中,定義了3 個塊類型:
typedef void (^ProgressBlock)(double progress); typedef void (^ResponseBlock)(MKNetworkOperation* operation); typedef void (^ErrorBlock)(NSError* error); |
在 YahooEngine中,咱們使用了一個新的塊類型:CurrencyResponseBlock,用以返回匯率。其定義以下:
typedef void (^CurrencyResponseBlock)(double rate); |
在其餘正式的 app 中,你應該定義本身的塊相似於CurrencyResponseBlock ,用以向 ViewController 返回數據。
步驟 3:處理數據
處理數據,包括將從服務器抓來的數據(例如 JSON/XML/plists)進行數據類型轉換。這應當在 Engine 中完成。注意,不要在控制器中完成。你的 Engine 應當將數據以適當的模型對象或模型對象的數組返回。在engine 中轉換 JSON/XML 爲模型——注意,適當保持關注分離,view controller 不該當知道任何用於訪問 JSON 節點的 key。這種思想主導了engine 的設計。許多網絡框架並不強制要求你服從關注分離,咱們這樣作,是由於咱們爲你考慮到了。
步驟 4:實現方法
如今,咱們來討論方法實現細節。要從 Yahoo 得到匯率信息,最簡單的是發起一個 GET 請求。下列宏用一對指定的貨幣格式化 URL 字串:
We will now discuss the implementationdetails of the method that calculates your currency exchange.
Getting currency information from Yahoo,is as simple as making a GET request.
I wrote a macro to format this URL for a given currency pair.
#define YAHOO_URL(__C1__, __C2__) [NSString stringWithFormat:@"d/quotes.csv?e=.csv&f=sl1d1t1&s=%@%@=X", __C1__, __C2__] |
按以下順序編寫 engine類方法:
根據參數準備 URL
建立一個 MKNetworkOperation 對象
設置方法參數
設置 operation 的 completion 塊和 error 塊(在 completation 塊中處理 response 並轉換爲模型)
可選地,添加一個 progress 塊(或者在 view controller 中作這個)
若是 operation 是下載,設置下載流(一般是文件)。這步也是可選的
當 operation 完成,處理結果並調用方法塊,並將數據返回給調用者。
示例代碼以下:
MKNetworkOperation *op = [selfoperationWithPath:YAHOO_URL(sourceCurrency, targetCurrency)
params:nil
httpMethod:@"GET"];
[op onCompletion:^(MKNetworkOperation*completedOperation)
{
DLog(@"%@", [completedOperation responseString]);
//do your processing here
completionBlock(5.0f);
}onError:^(NSError* error) {
errorBlock(error);
}];
[self enqueueOperation:op];
return op;
上述代碼格式化 URL 並建立了 MKNetworkOperation。設置完 completion 和 error 塊以後,將 operation 加入到隊列(經過父類的 enqueueOperation 方法),而後返回一個 operation 的引用。所以,若是你在 viewDidAppear 中調用這個方法,則在 viewWillDisappear 方法中取消operation。取消 operation 將釋放 operation 以便執行 queue 中用於其餘view 的 operation(牢記,在移動網絡中只有2 個 operation 能被同時進行,當 operation 再也不須要時取消它們能提高 app 的性能和速度)。
在 viewcontroller 中也能夠添加一個 progress 塊用以刷新UI。例如:
[self.uploadOperation onUploadProgressChanged:^(double progress) { DLog(@"%.2f", progress*100.0); self.uploadProgessBar.progress = progress; }]; |
MKNetworkEngine 也有一個只用 URL 建立 operation 的有用方法。所以第1行代碼也能夠寫成:
MKNetworkOperation *op = [self operationWithPath:YAHOO_URL(sourceCurrency, targetCurrency)]; |
注意,請求的 URL將自動添加上主機名(在 engine 實例化時指定的)。
像這樣的實用方法 MKNetworkEngine還有許多,你能夠查看頭文件。
例2:
上傳圖片到服務器 (例如 TwitPic)。
如今讓咱們看一個上傳圖片到服務器的例子。要上傳圖片,顯然要 operation 能編碼 multi-part 表單數據。 MKNetworkKit 使用相似 ASIHttpRequest 的方式。
你能夠很是簡單地經過MKNetworkOperation 的 addFile:forKey:方法將一個文件做爲請求中的 multi-part 表單數據提交。
MKNetworkOperation 也有一個方法,能夠將圖片以 NSData 的方式提交。即 addData:forKey: 方法,它能夠將圖片以NSData 的方法上傳到服務器。 (例如直接從相機中捕獲的圖片).
例3:
下載文件到本地目錄 (緩存)
使用MKNetworkKit 從服務器下載文件並保存到 iPhone 的本地目錄很是簡單。
只須要設置 MKNetworkOperation的 outputStream 。
[operation setDownloadStream:[NSOutputStream outputStreamToFileAtPath:@"/Users/mugunth/Desktop/DownloadedFile.pdf" append:YES]]; |
你能夠設置多個 outputStream 到一個 operation,將同一文件保存到幾個地方(例如其中一個是你的緩存目錄,另外一個用作你的工做目錄)。
例4:
緩存圖片的縮略圖
對於下載圖片,你可能須要提供一個絕對 URL 地址而不是一個路徑。
MKNetworkEngine 的operationWithURLString:params:httpMethod: 方法根據絕對 URL地址來建立網絡線程。
MKNetworkEngine 至關聰明。它會將同一個 URL 的屢次 GET 請求合併成一個,當 operation 完成時它會通知全部的塊。這顯著提高了抓取圖片 URL 以渲染縮略圖的速度.
子類化 MKNetworkEngine而後覆蓋圖片的緩存目錄及緩存的大小。若是你不想定製這兩者,你能夠直接調用 MKNetworkEngine中的方法來下載圖片。這是我極力推薦的。
緩存operation
MKNetworkKit 默認會緩存全部請求。你所須要的僅僅是在你本身的 engine 中打開它。當執行一個 GET 請求時,若是上次的 response 已緩存,相應的 completion 塊將用緩存的response 進行調用(瞬間)。要想知道 response 是否緩存,能夠調用 isCachedResponse 方法,以下所示:
[op onCompletion:^(MKNetworkOperation *completedOperation) { if([completedOperation isCachedResponse]) { DLog(@"Data from cache"); }else { DLog(@"Data from server"); } DLog(@"%@", [completedOperation responseString]); } onError:^(NSError* error) { errorBlock(error); }]; |
凍結operation
MKNetworkKit 的一個最有趣的特性是它內置的凍結 operation 特性。你只須要設置 operation 的 freeesable 屬性就能夠。幾乎什麼也不用作!
[op setFreezable:YES]; |
凍結是指 operation 在網絡被斷開時自動序列化並在網絡恢復後自動執行。例如當你離線時也可以進行收藏tweet 的操做,而後在你再次上線時 operation 自動恢復執行。
在應用程序進入後臺時,凍結的 operation 也會被持久化到磁盤。而後在應用程序回到前臺後自動恢復執行。
MKNetworkOperation 中的有用方法
以下所示,MKNetworkOperation 公開了一些有用的方法,你可從中獲取各類格式的 response 數據:
responseData
responseString
responseJSON (Only on iOS 5)
responseImage
responseXML
error
當 operation 執行完時,這些方法被用於獲取響應數據。若是格式不正確,方法會返回nil。例如,響應的數據明明是一個 HTML 格式,你用 responseImage 方法只會獲得 nil。只有 responseData 能保證不管什麼格式都返回正確,而其餘方法你必須確保和相應的repsone 類型匹配。
有用的宏
DLog 和 ALog 宏被無恥地從 Stackoverflow 剽竊來了,我找不到源做者。若是是你寫的,請告訴我。
關於GCD 的一點說明
由於網絡線程有可能會能被中止或優先處理,我果斷放棄了 GCD——GCD 的效率是比NSOperation 高,但它作不到這一點。我建議在你的網絡線程中也不要使用基於 GCD 的隊列。