Creating a Simple iOS REST Client Using HTTP-RPC
We take a look at how to make a simple REST client on iOS using the HTTP-RPC iOS client library. Come have a look!
Join the DZone community and get the full member experience.
Join For FreeHTTP-RPC is an open-source framework for simplifying development of REST applications. It allows developers to create and access web services using a convenient, RPC-like metaphor while preserving fundamental REST principles such as statelessness and uniform resource access. The project currently includes support for implementing REST services in Java and consuming services in Java, Objective-C/Swift, or JavaScript.
Although it is, of course, possible to use HTTP-RPC’s client libraries to invoke web services implemented using the server-side framework, this isn’t strictly required. The client APIs can actually be used to access any JSON-based REST service, regardless of server platform.
This article provides a demonstration of how the HTTP-RPC iOS client library can be used to invoke services provided by JSONPlaceholder, a “fake online REST API”.
Service API
JSONPlaceholder offers a collection of web services that simulate common REST operations on a variety of resource types such as “albums”, “photos”, and “users”. For example, the following URL retrieves a JSON document containing a list of simulated user records:
https://jsonplaceholder.typicode.com/users
The document is similar to the following:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
...
]
Additionally, the service provides a collection of simulated discussion posts, which can be retrieved on a per-user basis as follows:
https://jsonplaceholder.typicode.com/posts?userId=1
For example:
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
...
]
Sample Application
The sample application presents two views. The first one displays a list of users:
The second displays a list of posts by the selected user:
WSWebServiceProxy Class
The WSWebServiceProxy
class is used to invoke service operations. Internally, this class uses an instance of NSURLSession
to issue HTTP requests. NSJSONSerialization
is used to decode the response data.
Service methods are executed by calling invoke:path:arguments:resultHandler:
. This method takes the following arguments:
method
– the HTTP method to execute (e.g. “GET”, “POST”)path
– the resource patharguments
– a dictionary containing the request arguments as key/value pairsresultHandler
– a callback that will be invoked upon completion of the method
A convenience method is also provided for executing operations that don’t take any arguments.
Arguments are passed to the service either via the query string or in the request body, like an HTML form. Array arguments represent multi-value parameters and are handled similarly to <select multiple>
tags in HTML.
The result handler is a callback that is invoked upon completion of the request. If the operation completes successfully, the first argument will contain the result of the operation. If the operation fails, the second argument will be populated with an instance of NSError
describing the error that occurred.
For example, the following code might be used to invoke an operation that returns the sum of two numbers, specified by the “a” and “b” arguments. The service would return the value 6 in response:
// Get sum of "a" and "b"
serviceProxy.invoke("GET", path: "/math/sum", arguments: ["a": 2, "b": 4]) {(result, error) in
// result is 6
}
Application Delegate
An instance of WSWebServiceProxy
is created by the application delegate at startup:
private(set) static var serviceProxy: WSWebServiceProxy!
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
AppDelegate.serviceProxy = WSWebServiceProxy(session: NSURLSession.sharedSession(), serverURL: NSURL(string: "https://jsonplaceholder.typicode.com")!)
return true
}
The user and post view controllers discussed below use this proxy to invoke their respective service operations.
UserViewController Class
The UserViewController
class is responsible for presenting the list of users returned from /users. Internally, it maintains an array of objects representing the user list. In viewWillAppear:
, if the list has not already been loaded, it uses the service proxy to retrieve the user list from the server. An activity indicator is shown while the method is executing, and hidden once the request has completed:
class UserViewController: UITableViewController {
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
var users: [[String: AnyObject]]! = nil
...
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if (users == nil) {
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
activityIndicatorView.startAnimating()
AppDelegate.serviceProxy.invoke("GET", path: "/users") {(result, error) in
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
self.activityIndicatorView.stopAnimating()
if (error == nil) {
self.users = result as! [[String: AnyObject]]
self.tableView.reloadData()
} else {
NSLog(error!.localizedDescription)
}
}
}
}
...
}
A custom cell class is used to present the user details for each row:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (users == nil) ? 0 : users.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let user = users[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier(UserCell.self.description()) as! UserCell
cell.nameLabel.text = user["name"] as? String
cell.emailLabel.text = user["email"] as? String
return cell
}
Finally, when a user is selected, an instance of PostViewController
is created and pushed onto the navigation stack:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let postViewController = PostViewController()
postViewController.userID = users[indexPath.row]["id"] as! Int
navigationController?.pushViewController(postViewController, animated: true)
}
PostViewController Class
The PostViewController
is responsible for presenting the list of user posts returned from /posts. Like UserViewController
, it maintains an array of object representing the server response, and populates the array in viewWillAppear:
:
class PostViewController: UITableViewController {
var userID: Int!
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
var posts: [[String: AnyObject]]! = nil
...
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if (posts == nil) {
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
activityIndicatorView.startAnimating()
AppDelegate.serviceProxy.invoke("GET", path: "/posts", arguments: ["userId": userID]) {(result, error) in
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
self.activityIndicatorView.stopAnimating()
if (error == nil) {
self.posts = result as! [[String: AnyObject]]
self.tableView.reloadData()
} else {
NSLog(error!.localizedDescription)
}
}
}
}
...
}
Again, a custom cell class is used to present the details for each row:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (posts == nil) ? 0 : posts.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = posts[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier(PostCell.self.description()) as! PostCell
cell.titleLabel.text = post["title"] as? String
cell.bodyLabel.text = post["body"] as? String
return cell
}
More Information
This article provided a demonstration of how the HTTP-RPC iOS client library can be used to build a simple REST client application. The complete source code for the sample application can be found here.
The latest version of HTTP-RPC can be downloaded here. For more information, see the project README.
Published at DZone with permission of Greg Brown, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments