續AFNetworking2.0源碼解析<一>、<二>、<三>,本篇來看看AFURLResponseSerialization做的事情。
結構
AFURLResponseSerialization負責解析網絡返回數據,檢查數據是否合法,把NSData數據轉成相應的對象,內置的轉換器有json,xml,plist,image,用戶可以很方便地繼承基類AFHTTPResponseSerializer去解析更多的數據格式,AFNetworking這一套響應解析機制結構很簡單,主要就是兩個方法:
1.-validateResponse:data:error:
基類AFHTTPResponseSerializer的這個方法檢測返回的HTTP狀態碼和數據類型是否合法,屬性acceptableStatusCodes和acceptableContentTypes規定了合法的狀態碼和數據類型,例如JSONSerialization就把acceptableContentTypes設為@”application/json”, @”text/json”, @”text/javascript”,若不是這三者之一,就驗證失敗,返回相應的NSError對象。一般子類不需要重寫這個方法,只需要設置好acceptableStatusCodes和acceptableContentTypes就行了。
2.-responseObjectForResponse:data:error:
這個方法解析數據,把NSData轉成相應的對象,上層AFURLConnectionOperation會調用這個方法獲取轉換後的對象。
在解析數據之前會先調上述的validateResponse方法檢測HTTP響應是否合法,要注意的是即使這裡檢測返回不合法,也會繼續解析數據生成對象,因為有可能錯誤信息就在返回的數據裡。
如果validateResponse返回error,這裡的解析數據又出錯,這時有兩個error對象,怎樣返回給上層?這裡的處理是把解析數據的NSError對象保存到validateResponse NSError的userInfo裡,作為UnderlyingError,NSError專門給了個NSUnderlyingErrorKey作為這種錯誤包含錯誤的鍵值。
剩下的就是NSecureCoding相關方法了,如果子類增加了property,需要加上相應的NSecureCoding方法。
JSON解析
AFJSONResponseSerializer使用系統內置的NSJSONSerialization解析json,NSJSON只支持解析UTF8編碼的數據(還有UTF-16LE之類的,都不常用),所以要先把返回的數據轉成UTF8格式。這裡會嘗試用HTTP返回的編碼類型和自己設置的stringEncoding去把數據解碼轉成字符串NSString,再把NSString用UTF8編碼轉成NSData,再用NSJSONSerialization解析成對象返回。
上述過程是NSData->NSString->NSData->NSObject,這裡有個問題,如果你能確定服務端返回的是UTF8編碼的json數據,那NSData->NSString->NSData這兩步就是無意義的,而且這兩步進行了兩次編解碼,很浪費性能,所以如果確定服務端返回utf8編碼數據,就建議自己再寫個JSONResponseSerializer,跳過這兩個步驟。
此外AFJSONResponseSerializer專門寫了個方法去除NSNull,直接把對象裡值是NSNull的鍵去掉,還蠻貼心,若不去掉,上層很容易忽略了這個數據類型,判斷了數據是否nil沒判斷是否NSNull,進行了錯誤的調用導致core。
圖片解壓
當我們調用UIImage的方法imageWithData:方法把數據轉成UIImage對象後,其實這時UIImage對象還沒准備好需要渲染到屏幕的數據,現在的網絡圖像PNG和JPG都是壓縮格式,需要把它們解壓轉成bitmap後才能渲染到屏幕上,如果不做任何處理,當你把UIImage賦給UIImageView,在渲染之前底層會判斷到UIImage對象未解壓,沒有bitmap數據,這時會在主線程對圖片進行解壓操作,再渲染到屏幕上。這個解壓操作是比較耗時的,如果任由它在主線程做,可能會導致速度慢UI卡頓的問題。
AFImageResponseSerializer除了把返回數據解析成UIImage外,還會把圖像數據解壓,這個處理是在子線程(AFNetworking專用的一條線程,詳見AFURLConnectionOperation),處理後上層使用返回的UIImage在主線程渲染時就不需要做解壓這步操作,主線程減輕了負擔,減少了UI卡頓問題。
具體實現上在AFInflatedImageFromResponseWithDataAtScale裡,創建一個畫布,把UIImage畫在畫布上,再把這個畫布保存成UIImage返回給上層。只有JPG和PNG才會嘗試去做解壓操作,期間如果解壓失敗,或者遇到CMKY顏色格式的jpg,或者圖像太大(解壓後的bitmap太占內存,一個像素3-4字節,搞不好內存就爆掉了),就直接返回未解壓的圖像。
另外在代碼裡看到iOS才需要這樣手動解壓,MacOS上已經有封裝好的對象NSBitmapImageRep可以做這個事。
關於圖片解壓,還有幾個問題不清楚:
1.本來以為調用imageWithData方法只是持有了數據,沒有做解壓相關的事,後來看到調用堆棧發現已經做了一些解壓操作,從調用名字看進行了huffman解碼,不知還會繼續做到解碼jpg的哪一步。
2.以上圖片手動解壓方式都是在CPU進行的,如果不進行手動解壓,把圖片放進layer裡,讓底層自動做這個事,是會用GPU進行的解壓的。不知用GPU解壓與用CPU解壓速度會差多少,如果GPU速度很快,就算是在主線程做解壓,也變得可以接受了,就不需要手動解壓這樣的優化了,不過目前沒找到方法檢測GPU解壓的速度。
P.S. 關於圖片解壓,有篇挺挺不錯的文章:Avoiding Image Decompression Sickness
源碼注釋
AFURLResponseSerialization.m