1. 程式人生 > >Swift: 用Alamofire做http請求,用ObjectMapper解析JSON

Swift: 用Alamofire做http請求,用ObjectMapper解析JSON

not tis ati obj 有意 objects 映射 loaddata api

演示樣例代碼看最後。

跟不上時代的人突然間走在了時代的前列,果然有別樣的風景。首先歧視一下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或者網頁方式登錄微博之後從server返回的access_token。

另外一個必須的輸入參數就是加入微博應用的時候生成的app key。
2. https://api.weibo.com/2/statuses/friends_timeline.json請求的url。
這個url返回的就是你follow的好友的微博。

就是你一打開微博client看到的那些。
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"] } }

  1. 也就是我們說的json對象嵌套時的model類的繼承關系。
  2. 在這種繼承關系中須要十分註意的是。在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"] } }

  1. 內層的statuses數組直接調用內層json對象相應的model類的數組,也即是var statuses: [StatusModel]?
  2. 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() } })

  1. 使用Mapper<BaseModel>().map(response.result.value)方法來映射json串。

    這裏須要分開來看。Mapper<BaseModel>()初始化了一個Mapper對象。Mapper是一個泛型。類型參數就是我們定義的最外層的json對象相應的model類BaseModel。之後我們調用了這個初始化好的Mapper對象的map方法。

    這種方法的參數就是一個json串,也就是字符串類型的,可是這個字符串必須是json格式的。

    response.result.value取出了http請求之後返回的json串。

  2. map方法返回的是可空類型的。

    所以須要用if-let的方式檢查一下返回的值是否可用。

    在可用的情況下用where語句推斷返回的timeLine總數是否大於零。

    大於零才是有意義的,才刷新collection view。

演示樣例代碼在這裏。這裏沒有使用微博的API。而是用了Github的API來演示請求和JSON處理。

比較簡單。

只是Github奇葩的返回的結果就是一個JSON Array,竟然能夠使用ObjectMapper的mapArray方法一次搞定。這算是一個小坑。其它的都非經常規了。

to be continued…

Swift: 用Alamofire做http請求,用ObjectMapper解析JSON