Swift: Make http requests with Alamofire and parse JSON with ObjectMapper

Keywords: JSON Swift github Attribute

The example code looks at the end.

People who can not keep up with the times suddenly walk in the forefront of the times, and indeed there is a different landscape. First, despise AFNetworking. It's too hard to use. I don't want to encapsulate it, or write a lot of code.

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 request

But using alamofire is much simpler, such as:

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]) 
    .response { request, response, data, error in
         print(response) 
    }

It's all a GET request, but you can see that the Alamofire code is much smaller. This is also a comparison with AFNetworking 3. x, and if you use AFNetworking 2. x, the comparison of code volume is more obvious. For programmers, the simple and convenient API for calling methods is the user experience. Developer s also need to meet UE needs.

Let's get to the point. Next, make chestnuts with the time line for microblogging.

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
})

Here we use Alamofire to request the time line of Weibo.

  1. The time line requesting a microblog requires SSO or web page to log in to the microblog and return access_token from the server. Another required input parameter is the app key generated when adding a microblog application.
  2. https://api.weibo.com/2/statuses/friends_timeline.json The url requested.
    This url returns to your follow er's friend's microblog. That's what you see when you open a microblog account.
  3. We know that Alamofire can convert the data returned by the request into JSON, String and NSData. If it is processed as JSON, that is, using the responseJSON method, JSON data will be automatically converted into NSDictionary. We need to use strings later to match JSON strings with Model objects, so we use the method responseString.

If everything is set correctly, you will see the result as follows:

{
    "statuses": [
        {
            "created_at": "Tue May 31 17:46:55 +0800 2011",
            "id": 11488058246,
            "text": "Ask for attention.",
            "source": "<a href="http://Weibo.com "rel=" nofollow "> Sina Weibo </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": "Chaoyang District, Beijing",
                "description": "Fifty years of life is like a dream; if there is life and death, what regrets do the heroes have?",
                "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,      // Not supported for the time being
    "next_cursor": 11488013766,     // Not supported for the time being
    "total_number": 81655
}

This is part of the example given by Weibo. Let's see what we need. We need some text and some pictures. After that, the main content to be displayed is text or pictures.

analysis

We parse json with ObjectMapper. ObjectMapper is a two-way transformation tool. You can convert a json string into a model or a model into a json string.

Install ObjectMapper:

pod 'ObjectMapper', '~> 1.1'

ObjectMapper's analysis of json is done from outside to inside, and there is no special specification in the process of this layer's analysis. Every layer can't be less (which can be reduced by setting up an analytic path). Each layer needs to be equipped with an entity class.

The outermost layer is:

{
    "statuses": [
      ...
    ],
    "previous_cursor": 0,      
    "next_cursor": 11488013766,     
    "total_number": 81655
}

So the corresponding model definition is as follows:

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"]
    }
}

The most important thing is to import ObjectMapper first. Nothing can be done without this.

  1. The BaseModel class needs to implement the Mappable interface. The latter is the implementation of this protocol.
  2. Returns an initialization method for a possible empty object, which is not available for the time being.
  3. This method is the most critical. In this method, you specify which attribute in the model corresponds to the value of json. This part of the function can be automatically realized, which interested person can fork out to write one, also convenient for everyone to use.
  4. See below.

Deep down

The content of label 4 above is detailed here. The content we want to show is under status. So how should we deal with this part of the content? The json format of statuses is as follows:

{
    "statuses": [
      {
          "created_at": "Tue May 31 17:46:55 +0800 2011",
           "id": 11488058246,
           "text": "Ask for attention.",
           "source": "<a href="http://Weibo.com "rel=" nofollow "> Sina Weibo </a>"
           "favorited": false,
           "truncated": false,
           "in_reply_to_status_id": "",
           "in_reply_to_user_id": "",
           "in_reply_to_screen_name": "",
           "geo": null,
          ...
      }
    ],
}

There are two ways to process deep json data. One is to specify the corresponding relationship between json data and attributes in the mapping method. For example, mapping text in status in the BaseMode class can be written as follows:

class BaseModel {
  var text: String?

  required init?(_ map: Map) { 
  }

  func mapping(map: Map) {
    self.text <- map["statuses.text"]
  }
}

But that's wrong! Because status is an array, not an object. This is true only if statuses correspond to an object.

Modify the above code to fit the data situation.

class BaseModel {
  var text: String?

  required init?(_ map: Map) { 
  }

  func mapping(map: Map) {
    self.text <- map["status.0.text"]
  }
}

The number zero in the middle of self. text <- map ["statuses. 0. text"] indicates that the text attribute corresponds to the text value of the first element of the status array in json. But there are many JSON objects under statuses, and it is obviously not appropriate to parse them one by one. Needless to say, there are only two layers. How many wonderful API s return three or more layers?

Then there's the last one left. The model class of the inner JSON inherits the model class of the outer json. In this way, we define a model class for the JSON object corresponding to status as StatusModel. Because StatusModel corresponds to the inner JSON object, it is necessary to inherit the class of the outer JSON object, namely BaseModel. Named BaseModel at the beginning, it should have been exposed.

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. That's what we call the inheritance relationship of the model class when the json objects are nested.
  2. In this inheritance relationship, great attention should be paid to it. In the method invocation of the Mapable protocol, the corresponding method of the base class, super.init(map) and super.mapping(map) need to be invoked first. As for the mapping relationship of mapping method, the model class corresponding to each json object can only care about one object.

Then the status attribute in the outermost BaseModel class can give a correct and complete description.

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. The inner status array directly calls the array of the model class corresponding to the inner json object, namely var status: [StatusModel]?.
  2. In the mapping method, specify the relationship between attributes and json objects, here is statuses < - map ["statuses"].

So ObjectMapper knows how to parse json strings into corresponding class objects. In addition to the above, ObjectMapper has many other functions. If you need more information, you can check it out Official documents.

From http requests to returning data to parsing json strings, a series of actions can be fully linked. After the initial introduction to using the Alamofire request and successfully returning, we just printed out the strings. Now you can call the map method to match the json string with our defined model class.

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) method is used to map json strings. Here we need to look at it separately. Mapper < BaseModel >() initializes a Mapper object. Mapper is a generic type, and the type parameter is the BaseModel class corresponding to the outermost json object we define. Then we call the map method of the initialized Mapper object. The parameter of this method is a json string, that is, the string type, but the string must be in json format. response.result.value takes out the json string returned after the http request.
  2. The map method returns an empty type. So you need to check if the returned value is available in the form of if-let. where statement can be used to determine whether the total number of returned timeLine s is greater than zero. It is meaningful to be greater than zero before refreshing collection view.

The sample code is in the Here . Instead of using microblog APIs, Github APIs are used to demonstrate requests and JSON processing. It's relatively simple. But Github returns a JSON Array, which can be done once using ObjectMapper's mapArray method. This is a pit. Everything else is routine.

to be continued...

Posted by ierpe on Sun, 16 Dec 2018 18:54:03 -0800