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

示例代码看最后。

跟不上时代的人突然间走在了时代的前列,果然有别样的风景。首先鄙视一下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"]
    }
}
  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...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,319评论 0 12
  • ​每一种路上的相遇 都承载着千言万语 每一次眼角的泪水 都积攒着厚积薄发的努力 痛苦的必经之路 染满一地的鲜血 我...
    liyana的空谷梵音阅读 430评论 0 1
  • 十一假期的最后一天,明天就要上班。如同以往每个工作日前的夜晚,我又焦虑了。 现在,焦虑已经不能算个事儿了。在地铁上...
    骑象的朱力阅读 1,060评论 0 0