Creating a Simple iOS REST Client Using HTTP-RPC

HTTP-RPC is an open-source framework for simplifying development of REST applications. It allows developers to access REST-based 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 consuming web services in Objective-C/Swift, Java, and JavaScript. It provides a consistent client API that makes it easy to interact with services regardless of target device or operating system.

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 operations 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 path
  • arguments – a dictionary containing the request arguments as key/value pairs
  • resultHandler – 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!

var window: UIWindow?

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    AppDelegate.serviceProxy = WSWebServiceProxy(session: URLSession.shared, serverURL: URL(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, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let user = users[(indexPath as NSIndexPath).row]

    let cell = tableView.dequeueReusableCell(withIdentifier: 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, didSelectRowAt indexPath: IndexPath) {
    let postViewController = PostViewController()

    postViewController.userID = users[(indexPath as NSIndexPath).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 objects 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, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let post = posts[(indexPath as NSIndexPath).row]

    let cell = tableView.dequeueReusableCell(withIdentifier: 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.

Regular Expression to Validate a Comma-Separated List of Email Addresses

Recently, I needed to create a regular expression to validate the format of a comma-separated list of email addresses. Just thought I’d share the result in case it is of use to anyone. The pattern I came up with for matching a single address is as follows:

[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+

Note that I didn’t attempt to handle every possible combination of valid characters – only what I’d consider the most common ones.

The pattern is simply duplicated for the comma-separated list. Here’s an example of applying the pattern in Java:

Pattern addressListPattern = Pattern.compile(String.format("%1$s(,\\s*%1$s)*",
    "[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+"),
    Pattern.UNICODE_CHARACTER_CLASS);

System.out.println(addressListPattern.matcher("xyz").matches()); // false
System.out.println(addressListPattern.matcher("foo@bar.com").matches()); // true
System.out.println(addressListPattern.matcher("foo@bar.com, xyz").matches()); // false
System.out.println(addressListPattern.matcher("foo@b-ar.com, f.oo@b-ar.co.uk").matches()); // true
System.out.println(addressListPattern.matcher("føo@b-ar.com, f-oo@b-ar.co.uk").matches()); // true

Note that Pattern.UNICODE_CHARACTER_CLASS is not necessary (or supported) in Android, which supports Unicode character matching by default.

Hope it helps!

Using MarkupKit with Charts

For a while now, I’ve been meaning to see if the popular open-source Charts library would work with MarkupKit. I finally got around to trying it this morning.

It actually turned out to be much easier than I expected. All of the Charts demos are built using XIB files. All I had to do was create a markup document that mimicked the contents of a XIB file, including outlets and actions, and add the code to load the view from markup instead of the XIB. I chose to try to replicate the pie chart example:

To load the view declaration, I added the following method to the demo’s PieChartViewController class. No other changes to the controller were necessary:

- (void)loadView
{
    [self setView:[LMViewBuilder viewWithName:@"PieChartViewController" owner:self root:nil]];
}

The contents of PieChartViewController.xml are shown below. Note the use of the Charts prefix in the PieChartView declaration. This is necessary because the PieChartView class is defined in the Charts module:

<?xml version="1.0" encoding="UTF-8"?>

<!-- Pie chart demo -->
<LMColumnView layoutMarginsRelativeArrangement="false" backgroundColor="#f0f0f0">
    <!-- Options button -->
    <LMRowView layoutMarginTop="6" layoutMarginLeft="18" layoutMarginRight="18">
        <LMSpacer/>

        <UIButton style="systemButton" title="Options"
            titleLabel.font="HelveticaNeue-Light 17.0" tintColor="#4c8fbd"
            onTouchUpInside="optionsButtonTapped:"/>
    </LMRowView>

    <!-- Pie chart view -->
    <Charts.PieChartView id="chartView" weight="1"/>

    <!-- Sliders -->
    <LMColumnView layoutMarginLeft="8" layoutMarginRight="8" layoutMarginBottom="24">
        <LMRowView>
            <UISlider id="sliderX" minimumValue="1" maximumValue="25" value="1" weight="4"
                onValueChanged="slidersValueChanged:"/>

            <UITextField id="sliderTextX" weight="1"
                font="HelveticaNeue-Light 15.0"
                textAlignment="center"/>
        </LMRowView>

        <LMRowView>
            <UISlider id="sliderY" minimumValue="1" maximumValue="200" value="1" weight="4"
                onValueChanged="slidersValueChanged:"/>

            <UITextField id="sliderTextY" weight="1"
                font="HelveticaNeue-Light 15.0"
                textAlignment="center"/>
        </LMRowView>
    </LMColumnView>
</LMColumnView>

The results are shown below. They are virtually indistinguishable from the original (though the chart data differs slightly, since it appears to be randomly generated each time the app is run):

So, I was very happy to discover that, not only did MarkupKit work with the Charts library, I was able to use it as a drop-in replacement for the original XIB file in the demo application!

The latest version of MarkupKit can be downloaded here. See the project README for more information.

Installing MongoDB on 32-bit Ubuntu 15.10

I recently decided to install MongoDB on an old Mac Mini I use for testing. This particular device is too old to run the latest version of OS X, so I'm currently running the 32-bit version of Ubuntu 15.10 on it. Unfortunately, MongoDB no longer provides installation packages for 32-bit Linux distributions, so I had to set it up manually. Since this was a fairly cumbersome process, I thought I would share the steps I took in case they are of use to anyone:

  1. Download 32-bit MongoDB binaries:
    curl -O https://fastdl.mongodb.org/linux/mongodb-linux-i686-3.2.4.tgz
    tar -zxvf mongodb-linux-i686-3.2.4.tgz
  2. Copy MongoDB binaries to /usr/bin:
    sudo cp mongodb-linux-i686-3.2.4/bin/* /usr/bin/
  3. Download startup script:
    curl https://raw.githubusercontent.com/mongodb/mongo/master/debian/init.d > init.d
  4. Move startup script to /etc/init.d directory:
    sudo mv init.d /etc/init.d/mongod
    sudo chmod 755 /etc/init.d/mongod        
  5. Create configuration script /etc/mongod.conf:
    storage:
      dbPath: /var/lib/mongo
      journal:
        enabled: true
      engine: mmapv1
    
    systemLog:
      destination: file
      logAppend: true
      path: /var/log/mongodb/mongod.log
    
    processManagement:
      fork: true
    
    net:
      port: 27017
      bindIp: 0.0.0.0
  6. Add "mongodb" user:
    sudo useradd --home-dir /var/lib/mongo --shell /bin/false mongodb
    sudo passwd mongodb
  7. Create /var/lib/mongo directory:
    sudo mkdir /var/lib/mongo
    sudo chown -R mongodb /var/lib/mongo
    sudo chgrp -R mongodb /var/lib/mongo
  8. Create /var/log/mongodb directory:
    sudo mkdir /var/log/mongodb
    sudo chown -R mongodb /var/log/mongodb
    sudo chgrp -R mongodb /var/log/mongodb
  9. Create /var/run/mongod.pid file:
    sudo touch /var/run/mongod.pid
    sudo chown mongodb /var/run/mongod.pid
    sudo chgrp mongodb /var/run/mongod.pid
  10. Initialize service:
    sudo update-rc.d mongod defaults
  11. Restart server:
    sudo shutdown -r now

Java I/O Annoyances

While making some code updates the other day, I discovered a couple of potentially confusing features of the java.io package that I wasn't previously aware of:

  1. The PrintStream and PrintWriter classes consume exceptions. From the PrintWriter documentation:

    Methods in this class never throw I/O exceptions, although some of its constructors may. The client may inquire as to whether any errors have occurred by invoking checkError().

    Because I had incorrectly assumed that a PrintWriter would propagate any exceptions thrown by the underlying stream, I hadn't been calling checkError(). The result was that my servlet class, which was using the print writer returned by ServletResponse#getWriter(), failed to detect when a connection had been terminated by the client. The servlet simply continued writing to the output stream. Once I started calling checkError(), the response was correctly terminated:

    if (writer.checkError()) {
        throw new IOException("Error writing to output stream.");
    }

    I couldn't find any explanation as to why these two classes were written this way, while all of the other classes in java.io appear to simply propagate exceptions.

  2. The read(byte[], int, int) method of the InputStream class also consumes exceptions. From the Javadoc:

    The read(b, off, len) method for class InputStream simply calls the method read() repeatedly. If the first such call results in an IOException, that exception is returned from the call to the read(b, off, len) method. If any subsequent call to read() results in a IOException, the exception is caught and treated as if it were end of file.

    This is extremely misleading, since it completely obscures the fact that an error occurred and makes it appear as though the stream terminated normally.

So, even though this behavior is not what I had expected, at least it is documented, and is something I'll now be aware of when using these classes in the future.

Using Factory Methods to Simulate Map Literals in Java

Unlike many modern programming languages, Java does not support the notion of a "map literal"; that is, a map whose contents are declared using a dedicated language construct rather than being instantiated and populated via explicit API calls. For example, in Swift it is possible to create a dictionary (the Swift equivalent of a Java map) using the following syntax:

var dictionary = ["a": 1, "b": 2, "c": 3]

The result of executing this code is identical to the following, more verbose, version, which first allocates the dictionary and then populates it:

var dictionary = [String: Int]()

dictionary["a"] = 1
dictionary["b"] = 2
dictionary["c"] = 3

Similarly, an object can be constructed in JavaScript using the following literal syntax:

var object = {"a": 1, "b": 2, "c": 3};

or this more verbose equivalent:

var object = new Object();

object["a"] = 1;
object["b"] = 2;
object["c"] = 3;

Because Java does not support map literals, map instances must always be explicitly instantiated and populated. This is especially cumbersome if the map only needs to exist in the context of a single method call:

HashMap map = new HashMap();

map.put("a", 1);
map.put("b", 2);
map.put("c", 3);

process(map);

Fortunately, it is possible to approximate the behavior of map literals in Java using factory methods. For example:

// Create a map from a variable length array of map entries
@SafeVarargs
public static <K> Map<K, ?> mapOf(Map.Entry<K, ?>... entries) {
    HashMap<K, Object> map = new HashMap<>();

    for (Map.Entry<K, ?> entry : entries) {
        map.put(entry.getKey(), entry.getValue());
    }

    return Collections.unmodifiableMap(map);
}

// Create a map entry
public static <K> Map.Entry<K, ?> entry(K key, Object value) {
    return new AbstractMap.SimpleImmutableEntry<>(key, value);
}

Using these methods, the previous example can be reduced to a single line of code:

process(mapOf(entry("a", 1), entry("b", 2), entry("c", 3)));

It may not be quite as elegant as the Swift or JavaScript versions, but it is a lot more convenient than creating and populating the map an element at a time.

.gitignore for Xcode 7

I end up rewriting this file for every Xcode project I create, so I thought I'd post it in case it is of use to anyone:

**/.DS_Store
.xcodeproj/xcuserdata/
.xcodeproj/project.xcworkspace/xcuserdata/

Comments and suggestions for improvement are welcome!