跟不上時代的人突然間走在了時代的前列,果然有別樣的風景。首先鄙視一下AFNetworking。這個東西實在太難用了。不想封裝都不行,要不寫一大堆代碼。
NSURL *URL = [NSURL URLWithString:@"http://example.com/resources/123.json"]; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) { NSLog(@"JSON: %@", responseObject); } failure:^(NSURLSessionTask *operation, NSError *error) { NSLog(@"Error: %@", error); } ];
Http請求
但是用alamofire就簡單的很多了,如:
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]) .response { request, response, data, error in print(response) }
都是一個GET請求,但是可見的是Alamofire代碼量少很多。這也是和AFNetworking3.x比較了,如果你用的是AFNetworking2.x的話代碼量的對比更加明顯。對於程序員來說調用方法的API簡單方便就是用戶體驗。Developer們也是需要滿足UE的需要的。
下面開始進入正題。下面用請求微博的time line來做栗子。
parameters = ["access_token": weiboUserInfo.accessToken ?? "", "source": ConstantUtil.WEIBO_APPKEY] //1 Alamofire.request(.GET, "https://api.weibo.com/2/statuses/friends_timeline.json" //2 , parameters: parameters, encoding: .URL, headers: nil) .responseString(completionHandler: {response in print("response:- \(response)") //3 })
這里用Alamofire請求微博的time line。
1. 請求微博的time line就需要SSO或者網頁方式登錄微博之后從服務器返回的access_token。另外一個必須的輸入參數就是添加微博應用的時候生成的app key。
2. https://api.weibo.com/2/statuses/friends_timeline.json請求的url。
這個url返回的就是你follow的好友的微博。就是你一打開微博客戶端看到的那些。
3. 我們知道Alamofire可以把請求返回的數據轉化為JSON、String和NSData。如果是作為JSON來處理,也就是使用了responseJSON
方法的話,JSON數據會被自動轉化為NSDictionary
。我們后面需要用到字符串來實現json字符串和Model對象的匹配,所以我們用方法responseString
。
如果一切設置正確,你會看到這樣的結果:
{ "statuses": [ { "created_at": "Tue May 31 17:46:55 +0800 2011", "id": 11488058246, "text": "求關注。", "source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>", "favorited": false, "truncated": false, "in_reply_to_status_id": "", "in_reply_to_user_id": "", "in_reply_to_screen_name": "", "geo": null, "mid": "5612814510546515491", "reposts_count": 8, "comments_count": 9, "annotations": [], "user": { "id": 1404376560, "screen_name": "zaku", "name": "zaku", "province": "11", "city": "5", "location": "北京 朝陽區", "description": "人生五十年,乃如夢如幻;有生斯有死,壯士復何憾。", "url": "http://blog.sina.com.cn/zaku", "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1", "domain": "zaku", "gender": "m", "followers_count": 1204, ... } }, ... ], "ad": [ { "id": 3366614911586452, "mark": "AB21321XDFJJK" }, ... ], "previous_cursor": 0, // 暫時不支持 "next_cursor": 11488013766, // 暫時不支持 "total_number": 81655 }
以上是微博給出來的例子的一部分,我們來看看我們需要什么。我們需要一部分文字和一部分的圖片。之后要顯示的內容主要就是文字或者圖片。
解析
我們用ObjectMapper
解析json。ObjectMapper
是一個雙向的轉化工具。可以把json字符串轉化成model也可以把model轉化成json字符串。
安裝ObjectMapper
:
pod 'ObjectMapper', '~> 1.1'
ObjectMapper
對於json的解析都是從外往內進行的,這個層層解析的過程中一般沒有特殊指定的話每一層都不能少(可以通過制定解析路徑減少)。每一層都需要配備一個實體類。
最外面的一層是:
{ "statuses": [ ... ], "previous_cursor": 0, "next_cursor": 11488013766, "total_number": 81655 }
所以對應的model定義是這樣的:
import ObjectMapper class BaseModel: Mappable { // 1 var previousCursor: Int? var nextCursor: Int? //var statuses var totalNumber: Int? required init?(_ map: Map) { // 2 } func mapping(map: Map) { // 3 previousCursor <- map["previous_cursor"] nextCursor <- map["next_cursor"] //hasVisible <- map["hasvisible"] statuses <- map["..."] // 4 totalNumber <- map["total_number"] } }
最重要的是先import ObjectMapper
。沒有這個什么都干不了。
1. BaseModel
類需要實現Mappable
接口。后面就是這個protocol
的實現。
2. 返回可能為空對象的初始化方法,法暫時用不到。
3. 這個方法最關鍵了。在這個方法里指定json的值對應的是model里的哪個屬性。這部分功能可以自動實現,哪位有心人可以fork出來寫一個,也方便大家使用。
4. 請看下文。
在深入一層
上問的標簽4的內容我們在這里詳細介紹。我們要展示的內容都是在statuses下的。那么我們應該如何處理這部分的內容呢?statuses的json格式是這樣的:
{ "statuses": [ { "created_at": "Tue May 31 17:46:55 +0800 2011", "id": 11488058246, "text": "求關注。", "source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>", "favorited": false, "truncated": false, "in_reply_to_status_id": "", "in_reply_to_user_id": "", "in_reply_to_screen_name": "", "geo": null, ... } ], }
可以有兩個方式來處理深層的json數據。一個是在mapping
方法里指定json數據和屬性的對應關系。比如在BaseMode
類中映射statuses中的text可以這樣寫:
class BaseModel { var text: String? required init?(_ map: Map) { } func mapping(map: Map) { self.text <- map["statuses.text"] } }
但是這樣是錯誤的!因為statuses是一個數組,而不是一個對象。只有statuses對應的是一個對象的時候才適用於這個情況。
對上面的代碼進行修改,讓其適用於數據的情況。
class BaseModel { var text: String? required init?(_ map: Map) { } func mapping(map: Map) { self.text <- map["status.0.text"] } }
self.text <- map["statuses.0.text"]
中間的數字零說明text屬性對應的是json中的statuses數組的第一個元素的text的值。但是在statuses下會有很多個json對象,一個一個的挨個解析的方式顯然是不適合的。更不用說這才兩層,有多少奇葩的API返回的是三層甚至更多的?
那么就剩下最后的一種方法了。內層json的model類繼承外層的json的model類。按照這個方法那么我們為statuses對應的json對象定義一個model類為StatusModel
。由於StatusModel
對應的是內層的json對象,那么就需要繼承外層的json對象的類,也就是BaseModel
。剛開始就命名為BaseModel
應該是已經露餡了。
class StatusModel: BaseModel { // 1 var statusId: String? var thumbnailPic: String? var bmiddlePic: String? var originalPic: String? var weiboText: String? var user: WBUserModel? required init?(_ map: Map) { super.init(map) // 2 } override func mapping(map: Map) { super.mapping(map) // 2 statusId <- map["id"] thumbnailPic <- map["thumbnail_pic"] bmiddlePic <- map["bmiddle_pic"] originalPic <- map["original_pic"] weiboText <- map["text"] } }
- 也就是我們說的json對象嵌套時的model類的繼承關系。
- 在這種繼承關系中需要十分注意的是。在
Mappable
協議的方法的調用中需要先調用基類的對應方法,super.init(map)
和super.mapping(map)
。至於說mapping
方法的映射關系,每個json對象對應的model類只管這一個對象的就可以。
那么在最外層的BaseModel
類中的statuses屬性也就可以給出一個正確的完整的寫法了。
class BaseModel: Mappable { var previousCursor: Int? var nextCursor: Int? var hasVisible: Bool? var statuses: [StatusModel]? // 1 var totalNumber: Int? required init?(_ map: Map) { } func mapping(map: Map) { previousCursor <- map["previous_cursor"] nextCursor <- map["next_cursor"] hasVisible <- map["hasvisible"] statuses <- map["statuses"] // 2 totalNumber <- map["total_number"] } }
- 內層的statuses數組直接調用內層json對象對應的model類的數組,也即是
var statuses: [StatusModel]?
。 - 在
mapping
方法中指定屬性和json對象的關系,這里是statuses <- map["statuses"]
。
這樣ObjectMapper
就知道應該如何解析json字符串到對應的類對象中了。除了上面提到的,ObjectMapper
還有很多其他的功能。如果需要了解更多可以查看官方文檔。
那么從http請求,到返回數據,到解析json串的一系列動作就可以完整的聯結起來了。最開始介紹使用Alamofire請求並成功返回之后,我們只是把字符串打印了出來。現在可以調用map方法來匹配json串和我們定義好的model類了。
parameters = ["access_token": weiboUserInfo.accessToken ?? "", "source": ConstantUtil.WEIBO_APPKEY] Alamofire.request(.GET, "https://api.weibo.com/2/statuses/friends_timeline.json", parameters: parameters, encoding: .URL, headers: nil) .responseString(completionHandler: {response in print("response:- \(response)") let statuses = Mapper<BaseModel>().map(response.result.value) // 1 print("total number: \(statuses!.totalNumber)") if let timeLine = statuses where timeLine.totalNumber > 0 { // 2 self.timeLineStatus = timeLine.statuses self.collectionView?.reloadData() } })
- 使用
Mapper<BaseModel>().map(response.result.value)
方法來映射json串。這里需要分開來看。Mapper<BaseModel>()
初始化了一個Mapper對象。Mapper
是一個泛型,類型參數就是我們定義的最外層的json對象對應的model類BaseModel
。之后我們調用了這個初始化好的Mapper
對象的map
方法。這個方法的參數就是一個json串,也就是字符串類型的,但是這個字符串必須是json格式的。response.result.value
取出了http請求之后返回的json串。 map
方法返回的是可空類型的。所以需要用if-let
的方式檢查一下返回的值是否可用。在可用的情況下用where
語句判斷返回的timeLine總數是否大於零。大於零才是有意義的,才刷新collection view。