示例代碼看最后。
跟不上時(shí)代的人突然間走在了時(shí)代的前列,果然有別樣的風(fēng)景。首先鄙視一下AFNetworking。這個(gè)東西實(shí)在太難用了。不想封裝都不行,要不寫一大堆代碼。
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請(qǐng)求
但是用alamofire就簡(jiǎn)單的很多了,如:
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.response { request, response, data, error in
print(response)
}
都是一個(gè)GET請(qǐng)求,但是可見的是Alamofire代碼量少很多。這也是和AFNetworking3.x比較了,如果你用的是AFNetworking2.x的話代碼量的對(duì)比更加明顯。對(duì)于程序員來說調(diào)用方法的API簡(jiǎn)單方便就是用戶體驗(yàn)。Developer們也是需要滿足UE的需要的。
下面開始進(jìn)入正題。下面用請(qǐng)求微博的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請(qǐng)求微博的time line。
- 請(qǐng)求微博的time line就需要SSO或者網(wǎng)頁(yè)方式登錄微博之后從服務(wù)器返回的access_token。另外一個(gè)必須的輸入?yún)?shù)就是添加微博應(yīng)用的時(shí)候生成的app key。
-
https://api.weibo.com/2/statuses/friends_timeline.json請(qǐng)求的url。
這個(gè)url返回的就是你follow的好友的微博。就是你一打開微博客戶端看到的那些。 - 我們知道Alamofire可以把請(qǐng)求返回的數(shù)據(jù)轉(zhuǎn)化為JSON、String和NSData。如果是作為JSON來處理,也就是使用了
responseJSON方法的話,JSON數(shù)據(jù)會(huì)被自動(dòng)轉(zhuǎn)化為NSDictionary。我們后面需要用到字符串來實(shí)現(xiàn)json字符串和Model對(duì)象的匹配,所以我們用方法responseString。
如果一切設(shè)置正確,你會(huì)看到這樣的結(jié)果:
{
"statuses": [
{
"created_at": "Tue May 31 17:46:55 +0800 2011",
"id": 11488058246,
"text": "求關(guān)注。",
"source": "<a 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": "北京 朝陽區(qū)",
"description": "人生五十年,乃如夢(mèng)如幻;有生斯有死,壯士復(fù)何憾。",
"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, // 暫時(shí)不支持
"next_cursor": 11488013766, // 暫時(shí)不支持
"total_number": 81655
}
以上是微博給出來的例子的一部分,我們來看看我們需要什么。我們需要一部分文字和一部分的圖片。之后要顯示的內(nèi)容主要就是文字或者圖片。
解析
我們用ObjectMapper解析json。ObjectMapper是一個(gè)雙向的轉(zhuǎn)化工具??梢园裫son字符串轉(zhuǎn)化成model也可以把model轉(zhuǎn)化成json字符串。
安裝ObjectMapper:
pod 'ObjectMapper', '~> 1.1'
ObjectMapper對(duì)于json的解析都是從外往內(nèi)進(jìn)行的,這個(gè)層層解析的過程中一般沒有特殊指定的話每一層都不能少(可以通過制定解析路徑減少)。每一層都需要配備一個(gè)實(shí)體類。
最外面的一層是:
{
"statuses": [
...
],
"previous_cursor": 0,
"next_cursor": 11488013766,
"total_number": 81655
}
所以對(duì)應(yīng)的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。沒有這個(gè)什么都干不了。
-
BaseModel類需要實(shí)現(xiàn)Mappable接口。后面就是這個(gè)protocol的實(shí)現(xiàn)。 - 返回可能為空對(duì)象的初始化方法,法暫時(shí)用不到。
- 這個(gè)方法最關(guān)鍵了。在這個(gè)方法里指定json的值對(duì)應(yīng)的是model里的哪個(gè)屬性。這部分功能可以自動(dòng)實(shí)現(xiàn),哪位有心人可以fork出來寫一個(gè),也方便大家使用。
- 請(qǐng)看下文。
在深入一層
上問的標(biāo)簽4的內(nèi)容我們?cè)谶@里詳細(xì)介紹。我們要展示的內(nèi)容都是在statuses下的。那么我們應(yīng)該如何處理這部分的內(nèi)容呢?statuses的json格式是這樣的:
{
"statuses": [
{
"created_at": "Tue May 31 17:46:55 +0800 2011",
"id": 11488058246,
"text": "求關(guān)注。",
"source": "<a rel="nofollow">新浪微博</a>",
"favorited": false,
"truncated": false,
"in_reply_to_status_id": "",
"in_reply_to_user_id": "",
"in_reply_to_screen_name": "",
"geo": null,
...
}
],
}
可以有兩個(gè)方式來處理深層的json數(shù)據(jù)。一個(gè)是在mapping方法里指定json數(shù)據(jù)和屬性的對(duì)應(yīng)關(guān)系。比如在BaseMode類中映射statuses中的text可以這樣寫:
class BaseModel {
var text: String?
required init?(_ map: Map) {
}
func mapping(map: Map) {
self.text <- map["statuses.text"]
}
}
但是這樣是錯(cuò)誤的!因?yàn)閟tatuses是一個(gè)數(shù)組,而不是一個(gè)對(duì)象。只有statuses對(duì)應(yīng)的是一個(gè)對(duì)象的時(shí)候才適用于這個(gè)情況。
對(duì)上面的代碼進(jìn)行修改,讓其適用于數(shù)據(jù)的情況。
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"]中間的數(shù)字零說明text屬性對(duì)應(yīng)的是json中的statuses數(shù)組的第一個(gè)元素的text的值。但是在statuses下會(huì)有很多個(gè)json對(duì)象,一個(gè)一個(gè)的挨個(gè)解析的方式顯然是不適合的。更不用說這才兩層,有多少奇葩的API返回的是三層甚至更多的?
那么就剩下最后的一種方法了。內(nèi)層json的model類繼承外層的json的model類。按照這個(gè)方法那么我們?yōu)閟tatuses對(duì)應(yīng)的json對(duì)象定義一個(gè)model類為StatusModel。由于StatusModel對(duì)應(yīng)的是內(nèi)層的json對(duì)象,那么就需要繼承外層的json對(duì)象的類,也就是BaseModel。剛開始就命名為BaseModel應(yīng)該是已經(jīng)露餡了。
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對(duì)象嵌套時(shí)的model類的繼承關(guān)系。
- 在這種繼承關(guān)系中需要十分注意的是。在
Mappable協(xié)議的方法的調(diào)用中需要先調(diào)用基類的對(duì)應(yīng)方法,super.init(map)和super.mapping(map)。至于說mapping方法的映射關(guān)系,每個(gè)json對(duì)象對(duì)應(yīng)的model類只管這一個(gè)對(duì)象的就可以。
那么在最外層的BaseModel類中的statuses屬性也就可以給出一個(gè)正確的完整的寫法了。
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"]
}
}
- 內(nèi)層的statuses數(shù)組直接調(diào)用內(nèi)層json對(duì)象對(duì)應(yīng)的model類的數(shù)組,也即是
var statuses: [StatusModel]?。 - 在
mapping方法中指定屬性和json對(duì)象的關(guān)系,這里是statuses <- map["statuses"]。
這樣ObjectMapper就知道應(yīng)該如何解析json字符串到對(duì)應(yīng)的類對(duì)象中了。除了上面提到的,ObjectMapper還有很多其他的功能。如果需要了解更多可以查看官方文檔。
那么從http請(qǐng)求,到返回?cái)?shù)據(jù),到解析json串的一系列動(dòng)作就可以完整的聯(lián)結(jié)起來了。最開始介紹使用Alamofire請(qǐng)求并成功返回之后,我們只是把字符串打印了出來。現(xiàn)在可以調(diào)用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>()初始化了一個(gè)Mapper對(duì)象。Mapper是一個(gè)泛型,類型參數(shù)就是我們定義的最外層的json對(duì)象對(duì)應(yīng)的model類BaseModel。之后我們調(diào)用了這個(gè)初始化好的Mapper對(duì)象的map方法。這個(gè)方法的參數(shù)就是一個(gè)json串,也就是字符串類型的,但是這個(gè)字符串必須是json格式的。response.result.value取出了http請(qǐng)求之后返回的json串。 -
map方法返回的是可空類型的。所以需要用if-let的方式檢查一下返回的值是否可用。在可用的情況下用where語句判斷返回的timeLine總數(shù)是否大于零。大于零才是有意義的,才刷新collection view。
示例代碼在這里。這里沒有使用微博的API,而是用了Github的API來演示請(qǐng)求和JSON處理。比較簡(jiǎn)單。不過Github奇葩的返回的結(jié)果就是一個(gè)JSON Array,居然可以使用ObjectMapper的mapArray方法一次搞定。這算是一個(gè)小坑。其他的都很常規(guī)了。