- 這篇筆記是在 AFN v0.10.1 時候寫的,AFN v1.0 以後加入了不少新東西,比如 SSL 支持,不過整體結構沒有變化。
- 後續跟進了一篇 AFNetworking Notes 2
上圖來自 @mattt 對 AFN 的介紹:Everybody Loves AFNetworking And So Can You!. 學習 AFN,簡單記錄一下以加深自己理解。
AFN 的基礎部分是 AFURLConnectionOperation,一個 NSOperation subclass,實現了 NSURLConnection 相關的 delegate+blocks,網絡部分是由 NSURLConnection 完成,然後利用 NSOperation 的 state (isReady→isExecuting→isFinished) 變化來進行網絡控制。網絡請求是在一個指定的線程(networkRequestThread)完成。
AFURLConnectionOperation 是一個很純粹的網絡請求 operation,可以對他進行 start/cancel/pause/resume 操作,可以獲取對應的 NSURLRequest 和 NSURLResponse 數據。支持 NSInputStream/NSOutputStream,提供了 uploadPress 和 downloadProgress 以方便其他使用。
1 2 3 4 5 6 |
|
插播:@mattt 在 NSHipster 裏有一篇 NSOperation 詳細介紹了 NSOperation 的 state、priority、dependency 等,對理解 AFURLConnectionOperation 很有幫助。
理解了 AFURLConnectionOperation 再看 AFHTTPRequestOperation 就簡單很多。AFHTTPRequestOperation 是 AFURLConnectionOperation 的子類,針對 HTTP+HTTPS 協議做了一層封裝,比如 statusCode、Content-Type 等,添加了請求成功和失敗的回調 block,提供了 addAcceptableContentTypes:
以方便上層使用。
1 2 3 4 5 6 7 8 |
|
AFJSONRequestOperation 是 AFHTTPRequestOperation 的子類,針對 JSON 類型請求做了特殊處理,在有了 AFHTTPRequestOperation+AFURLConnectionOperation 的基礎工作後,AFJSONRequestOperation 已經非常方便直接使用了。指定 acceptableContentTypes:
以支持
JSON,responseJSON
直接返回已經解析好的
JSON 數據對象。下載到 JSON 數據後在一單獨線程 queue(json_request_operation_processing_queue)對 JSON 數據進行解析處理,處理完成後由主線程回調 success block。
AFN 的 JSON encode/decode 處理做的非常巧妙,現在有很多 JSON 解析庫,第三方的 JSONKit、SBJSON 等,iOS 5+ 自帶的 NSJSONSerialization,不同的項目可能會因爲不同的需求而用不同的庫,AFN 就封裝了一個 AFJSONUtilities,提供 AFJSONEncode
和 AFJSONDecode
兩個方法,通過 NSClassFromString
和 NSSelectorFromString
來查找項目中使用的
JSON 庫然後進行 encode/decode。
1 2 3 4 5 6 7 8 |
|
AFXMLRequestOperation/AFPropertyListRequestOperation/AFImageRequestOperation 和 AFJSONRequestOperation 類似,針對 XML、Plist、image 類型請求做了一些處理。其中 AFImageRequestOperation 額外有一個 imageProcessingBlock,取到圖片後可以在一個單獨線程 queque 對圖片進行處理,比如縮放、切圓角、圖片特效等,然後再交給 main_queue success block.
AFN 還提供了一個 UIImageView+AFNetworking category,可以用 setImageWithURL:
來設置圖片。這個
cagetory 和 SDWebImage 類似但更簡單一些,圖片下載由 AFN 完成,圖片緩存由 NSCache 處理。
直接用上面這些已經可以方便的做網絡請求,AFN 在這些基礎上還提供了一個 AFHTTPClient,把 HTTP 請求的 Headers、User-Agent 等再次包裝,方便使用。AFHTTPClient 是一個單例,對請求參數做了 URL 編碼;維護一個 NSOperationQueue,不同的請求生成各自的 AFHTTPRequestOperation 然後 enqueueHTTPRequestOperation:
添加的隊列順序執行;registerHTTPOperationClass:
方法用來註冊上面的
JSON/XML/Plist/image operation,拿到請求結果後交給對應的 operation 處理。AFHTTPClient 還針對 GET/POST/HEAD/PUT/DELETE 等不同的請求做了不同的 URL 參數和 Headers 處理,包括 multipart/form-data 類型。
AFHTTPClient 支持批量添加 operations,生成一個 batchedOperation,把所有 operations 作爲 batchedOperation 的 dependency,再依次把所有 operations 和 batchedOperation 都添加到 operationQueue,這樣每一個 operation 完成後都可以做一個 progressBlock 來返回當前已完成的 operations 數和總數,等所有 operations 都完成後會做 batchedOperation 的 completionBlock,就可以在這一批 operations 都完成後做一些善後處理。
AFHTTPClient 提倡對同一應用(同一 baseURL)的網絡請求封裝自己的 HTTPClient 子類,這樣會方便很多。參考 WBKHTTPClient.
AFN 還提供了很多模塊,可以很方便的和 AFN 整合做一些工作,比如 OAuth,Amazon S3 等,詳見 AFNetworking-Extensions.
AFN 作者 @mattt 做東西很有自己一套思想在裏面,推薦 What I Learned From AFNetworking’s GitHub Issues,視頻。
AFNetworking 學習筆記 的後續,記錄一些 AFN 比較隱蔽的知識點。
AFN 的設計過於理想化
AFN 的架構設計非常棒,使用起來也很簡單,但一些設計過於理想化,在實際開發中會有一些條件不能滿足,這時候 AFN 就會出現一些“坑”。
1. 緩存策略
NSURLRequest 默認的緩存策略是 NSURLRequestUseProtocolCachePolicy
,網絡請求是否用緩存是由
HTTP Cache-Control 決定,而在實際開發中由於種種原因(服務端爲了簡化系統等),接口的緩存時間都設置的非常長或者不準,這種情況下就會出現服務端數據更新但是 AFN 拿到的還是舊數據,因爲他直接讀的緩存。
得益於 AFN 優秀的架構設計,這個問題也很好解決,繼承 AFHTTPClient 然後重寫 requestWithMethod:path:parameters:
:
1 2 3 4 5 6 7 |
|
2. Response 類型判斷
以 AFJSONRequestOperation 爲例,只有 Content-Type 是 @"application/json",
@"text/json", @"text/javascript"
或 URL pathExtension 是 json 的纔會被認爲是 JSON 類型,其他都不認。很多服務端接口都沒有 Content-Type 返回或者直接丟一個 text/html
,請求也不是
json 結尾,但返回內容確實又是 JSON 數據,這時候 AFN 就很無力。
上面這兩個問題的根本原因是服務端由於各種各樣的問題不能嚴格按照 HTTP 要求返回正確格式的內容,造成 AFN 無法按照標準去接收解析。責任雖不在客戶端開發,但實際開發中確實存在這種情況,這個時候就需要客戶端去迂迴解決,好在 AFN 的架構設計很容易擴展。
AFN vs ASI
AFN 已經取代 ASIHTTPRequest(ASI) 成爲 iOS 開發中首選的網絡庫,但不能說 AFN 就完勝 ASI,比如這篇 對比iOS網絡組件:AFNetworking VS ASIHTTPRequest,AFN 在易用性上勝出,在性能上並沒有 ASI 好(因爲 ASI 是直接用 CFNetwork 底層而 AFN 是用 NSURLConnection)。
就我自己實際開發來說,AFN 最大的不便是沒有 synchronous 請求方式,只支持異步請求。很多時候我們只是想發一個請求,無需返回處理,這種情況下 AFN 這種自定義 HTTPClient 的方式就過於複雜。
最近發現了一個網絡庫 STHTTPRequest,基於 NSURLConnection,支持 synchronous+asynchronous blocks,支持文件上傳,非常簡單輕量的封裝,值得一試。