Over a million developers have joined DZone.

A Practical Example Using MarkupKit and HTTP-RPC

See how you can use both projects to create a real iOS application

· Mobile Zone

Visually compose APIs with easy-to-use tooling. Learn how IBM API Connect provides near-universal access to data and services both on-premises and in the cloud, brought to you in partnership with IBM.

Author's NoteUpdated 7/16/16 for MarkupKit 2.2 and HTTP-RPC 3.0

In my spare time, I work on two open-source projects: MarkupKit and HTTP-RPC. MarkupKit allows developers to construct iOS applications declaratively using a human-readable markup language, and HTTP-RPC simplfies development of REST applications using a convenient, RPC-like metaphor.

I've written a few articles highlighting some of each project's key features independently, so I thought I'd put together an example that demonstrates how they can be used together to implement a complete application. The example simulates a connected air conditioner that can be controlled remotely via an iOS client app. The server-side Java version of HTTP-RPC is used to implement the simulated air conditioner, and MarkupKit is used to create the user interface for the iOS client:

Image title

The iOS app uses the Objective-C HTTP-RPC client library to communicate with the server.

Service Implementation

The simulated air conditioner is implemented as an HTTP-RPC web service, which provides methods for remotely managing the unit. The service class is defined as follows (it simply logs messages to the console to simulate the unit's behavior):

public class AirConditionerService extends WebService {
    private static Status status = new Status();

    @RPC(method="GET", path="status")
    public Map<String, ?> getStatus() {
        return new BeanAdapter(status);

    @RPC(method="PUT", path="status")
    public void setOnStatus(boolean on) {
        System.out.println("Setting unit power to " + (on ? "on" : "off"));


    @RPC(method="PUT", path="status")
    public void setTemperatureStatus(int temperature) {
        if (temperature < 32 || temperature > 96) {
            throw new IllegalArgumentException("Invalid temperature.");

        System.out.println("Setting unit temperature to " + temperature);


    @RPC(method="PUT", path="status")
    public void setFanSpeedStatus(int fanSpeed) {
        if (fanSpeed < 1 || fanSpeed > 3) {
            throw new IllegalArgumentException("Invalid fan speed.");

        System.out.println("Setting unit fan speed to " + fanSpeed);


Status is a simple POJO ("plain old Java object") type defined as follows:

public class Status {
    private boolean on = false;
    private int temperature = 70;
    private int fanSpeed = 1;

    public boolean isOn() {
        return on;

    public void setOn(boolean on) {
        this.on = on;

    public int getTemperature() {
        return temperature;

    public void setTemperature(int temperature) {
        this.temperature = temperature;

    public int getFanSpeed() {
        return fanSpeed;

    public void setFanSpeed(int fanSpeed) {
        this.fanSpeed = fanSpeed;

Client Implementation

The client application (written in Swift) allows the user to turn the simulated air conditioner on and off and set the unit's temperature and fan speed. It is implemented using a static table view whose contents are defined in markup.

The table view contains three sections: power, temperature, and fan speed. The power section contains a single on/off switch. The temperature section contains a label that displays the current temperature along with a stepper control that is used to increase or decrease the temperature. The fan speed section contains a list of three speeds, only one of which may be selected at a time:

<?properties {
    "cell.header": {
        "textLabel.font": "subheadline",
        "textLabel.textColor": "#808080"

<LMTableView style="groupedTableView">
    <!-- Power -->
    <UITableViewCell textLabel.text="@power" selectionStyle="none">
        <UISwitch id="onSwitch" onValueChanged="togglePower:"/>

    <!-- Temperature -->
    <UITableViewCell class="cell.header" textLabel.text="@temperature"/>

    <UITableViewCell id="temperatureCell" selectionStyle="none">
        <UIStepper id="temperatureStepper" minimumValue="32" maximumValue="96" onValueChanged="updateTemperature:"/>

    <!-- Fan Speed -->
    <?sectionName fanSpeed?>
    <?sectionSelectionMode singleCheckmark?>

    <UITableViewCell class="cell.header" textLabel.text="@fanSpeed"/>

    <UITableViewCell textLabel.text="@low"/>
    <UITableViewCell textLabel.text="@medium"/>
    <UITableViewCell textLabel.text="@high"/>

The markup uses inline template properties to style the section headers. Localizable content is defined in Localizable.strings:

"title" = "Air Conditioner Remote";

"power" = "Power";

"temperature" = "Temperature";

"fanSpeed" = "Fan Speed";
"high" = "High";
"medium" = "Medium";
"low" = "Low";

"serviceErrorTitle" = "Error";
"serviceErrorMessage" = "An error occurred while communicating with the unit.";

The main view controller coordinates the interaction between the user interface and the air conditioner service. At startup, the controller creates an instance of WSWebServiceProxy that it uses to communicate with the service:

override func viewDidLoad() {

    title = NSBundle.mainBundle().localizedStringForKey("title", value: nil, table: nil)

    serviceProxy = WSWebServiceProxy(session: NSURLSession.sharedSession(), serverURL: NSURL(string: "http://localhost:8080")!)

When the view appears, the controller calls the service's getStatus() method to retreive the air conditioner's current state, and updates the UI to reflect this state:

override func viewWillAppear(animated: Bool) {
    serviceProxy.invoke("GET", path: "/ac-service/ac/status") {(result, error) in
        if (error == nil) {
            let status = result as! NSDictionary

            // Update power
            self.onSwitch.on = status["on"] as! Bool

            // Update temperature
            self.temperatureStepper.value = status["temperature"] as! Double


            // Update fan speed
            let fanSpeed = status["fanSpeed"] as! Int
            let fanSpeedSection = self.tableView.sectionWithName(ViewController.FanSpeedSectionName)

            self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: fanSpeed - 1, inSection: fanSpeedSection))!.checked = true
        } else {

The handleServiceError: method is called if the remote method call fails. This method simply displays an alert to the user:

func handleServiceError(error: NSError) {
    let mainBundle = NSBundle.mainBundle()

    let alertController = UIAlertController(title: mainBundle.localizedStringForKey("serviceErrorTitle", value: nil, table: nil),
        message: mainBundle.localizedStringForKey("serviceErrorMessage", value: nil, table: nil),
        preferredStyle: .Alert)

    alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))

    self.presentViewController(alertController, animated: true, completion: nil)

The controller responds to user input on the various UI elements by invoking service methods to update the air conditioner's state. For example, the togglePower: method is called in response to a value change event on the power switch:

@IBAction func togglePower(sender: UISwitch) {
    serviceProxy.invoke("PUT", path: "/ac-service/ac/status", arguments: ["on": sender.on]) {(result, error) in
        if (error != nil) {

The updateTemperature: method is called in response to a value change event on the temperature stepper:

@IBAction func updateTemperature(sender: UIStepper) {
    let temperature = Int(sender.value)

    serviceProxy.invoke("PUT", path: "/ac-service/ac/status", arguments: ["temperature": temperature]) {(result, error) in
        if (error != nil) {


func updateTemperatureLabel() {
    temperatureCell.textLabel!.text = "\(Int(temperatureStepper.value))° F"

Finally, the tableView:didSelectRowAtIndexPath: method is called in response to a table view selection. If a selection occurs in the fan speed section, the controller invokes the service method to update the unit's fan speed:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if let sectionName = tableView.nameForSection(indexPath.section) {
        if (sectionName == ViewController.FanSpeedSectionName) {
            let fanSpeed = indexPath.row + 1

            serviceProxy.invoke("PUT", path: "/ac-service/ac/status", arguments: ["fanSpeed": fanSpeed]) {(result, error) in
                if (error != nil) {


This example provided a practical example of how MarkupKit and HTTP-RPC can be used together to implement a complete application. For more information, please see the project documentation.

The Mobile Zone is brought to you in partnership with Strongloop and IBM.  Visually compose APIs with easy-to-use tooling. Learn how IBM API Connect provides near-universal access to data and services both on-premises and in the cloud.


Published at DZone with permission of Greg Brown, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}