HTTP-RPC 5.3 Released

HTTP-RPC 5.3 is now available for download. This release adds support for typed result set iteration.

As previously discussed, HTTP-RPC's ResultSetAdapter class can be used to optimize transformation of JDBC result set data to JSON. The new adapt() method the 5.3 release adds to ResultSetAdapter can be used to facilitate type-safe iteration of query results. This method takes a ResultSet and an interface type as arguments, and returns an Iterable of the given type representing the rows in the result set.

For example, this interface might be used to model the results of a query on the "pet" table from the MySQL sample database:

public interface Pet {
    public String getName();
    public String getOwner();
    public String getSpecies();
    public String getSex();
    public Date getBirth();
}

The following service method uses adapt() to create an iterable sequence of Pet values. It wraps the adapter's iterator in a stream, and then uses the stream to calculate the average age of all pets in the database. The getBirth() method declared by the Pet interface is used to retrieve each pet's age in epoch time. The average value is converted to years at the end of the method:

@RequestMethod("GET")
@ResourcePath("/average-age")
public double getAverageAge() throws SQLException {
    Date now = new Date();

    double averageAge;
    try (Connection connection = DriverManager.getConnection(DB_URL);
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("SELECT birth FROM pet")) {
        Iterable<Pet> pets = ResultSetAdapter.adapt(resultSet, Pet.class);

        Stream<Pet> stream = StreamSupport.stream(pets.spliterator(), false);

        averageAge = stream.mapToLong(pet -> now.getTime() - pet.getBirth().getTime()).average().getAsDouble();
    }

    return averageAge / (365.0 * 24.0 * 60.0 * 60.0 * 1000.0);
}

For more information, see the project README.

Introducing Kilo

Kilo is an open-source framework for consuming REST services in iOS or tvOS. It is extremely lightweight and provides a convenient, callback-based interface that makes it easy to interact with remote APIs.

For example, the following code snippet shows how a client application might access a simple service that returns a friendly greeting. The request is executed asynchronously, and the result is printed when the call returns:

webServiceProxy.invoke(.get, path: "/hello") { (result: String?, error: Error?) in
    if let greeting = result {
        print(greeting) // "Hello, World!"
    }
}

The project’s name comes from the nautical K or Kilo flag, which means “I wish to communicate with you”:

This article introduces the Kilo framework and provides an overview of its key features.

WebServiceProxy Class

Kilo is distributed as a universal binary that will run in the iOS simulator as well as on an actual device. The framework contains a single class named WebServiceProxy that is used to issue API requests to the server.

Service proxies are initialized via init(session:serverURL:), which takes the following arguments:

  • session – a URLSession instance that is used to create service requests
  • serverURL – the base URL of the service

A service operation is initiated via one of the following methods:

public func invoke(_ method: Method, path: String,
    arguments: [String: Any] = [:], content: Data? = nil, contentType: String? = nil,
    resultHandler: @escaping (_ result: T?, _ error: Error?) -> Void) -> URLSessionTask? { ... }

public func invoke(_ method: Method, path: String,
    arguments: [String: Any] = [:], content: Data? = nil, contentType: String? = nil,
    resultHandler: @escaping (_ result: T?, _ error: Error?) -> Void) -> URLSessionTask? { ... }

public func invoke(_ method: Method, path: String,
    arguments: [String: Any] = [:], content: Data? = nil, contentType: String? = nil,
    responseHandler: @escaping (_ content: Data, _ contentType: String?) throws -> T?,
    resultHandler: @escaping (_ result: T?, _ error: Error?) -> Void) -> URLSessionTask? { ... }

All three methods accept the following arguments:

  • method – the HTTP method to execute
  • path – the path to the requested resource
  • arguments – a dictionary containing the method arguments as key/value pairs
  • content – an optional Data instance representing the body of the request
  • contentType – an optional string value containing the MIME type of the content
  • resultHandler – a callback that will be invoked upon completion of the method

The first version of the method uses JSONSerialization to decode response data. The second uses JSONDecoder to return a decodable value. The third version accepts an additional responseHandler argument to facilitate decoding of custom response content (for example, a UIImage).

All three methods return an instance of URLSessionTask representing the invocation request. This allows an application to cancel a task, if necessary.

Arguments

Like HTML forms, arguments are submitted either via the query string or in the request body. Arguments for GET, PUT, PATCH, and DELETE requests are always sent in the query string.

POST arguments are typically sent in the request body, and may be submitted as either “application/x-www-form-urlencoded” or “multipart/form-data” (determined via the service proxy’s encoding property). However, if a custom body is specified via the content parameter, POST arguments will be sent in the query string.

Any value that provides a description property may be used as an argument. This property is generally used to convert the argument to its string representation. However, Date instances are automatically converted to a 64-bit integer value representing epoch time (the number of milliseconds that have elapsed since midnight on January 1, 1970).

Additionally, array instances represent multi-value parameters and behave similarly to tags in HTML. Further, when using the multi-part form data encoding, instances of URL represent file uploads and behave similarly to tags in HTML forms. Arrays of URL values operate similarly to tags.

Return Values

The result handler is called upon completion of the operation. If successful, the first argument will contain a deserialized representation of the content returned by the server, and the second argument will be nil. Otherwise, the first argument will be nil, and the second will be populated with an Error instance describing the problem that occurred.

Note that, while service requests are typically processed on a background thread, result handlers are always executed on the application’s main thread. This allows result handlers to update the user interface directly, rather than posting a separate update operation to the main queue.

If the server returns an error response, a localized description of the error will be provided in the localized description of the error parameter. Further, if the error is returned with a content type of “text/plain”, the response body will be returned in the error’s debug description.

Example

The following code snippet demonstrates how the WebServiceProxy class might be used to access the operations of a simple math service:

// Create service proxy
let webServiceProxy = WebServiceProxy(session: URLSession.shared, serverURL: URL(string: "http://localhost:8080")!)

// Get sum of "a" and "b"
webServiceProxy.invoke(.get, path: "/math/sum", arguments: [
    "a": 2,
    "b": 4
]) { (result: Int?, error: Error?) in
    // result is 6
}

// Get sum of all values
webServiceProxy.invoke(.get, path: "/math/sum", arguments: [
    "values": [1, 2, 3, 4]
]) { (result: Int?, error: Error?) in
    // result is 10
}

Additional Information

This article introduced the Kilo framework and provided an overview of its key features. For additional information, see the the project README.

 

Efficiently Transforming JDBC Query Results to JSON

A lot of enterprise data is stored in relational databases and accessed via SQL queries. Many web services are little more than HTTP-based wrappers around such queries.

Unfortunately, transforming query results to JSON so it can be consumed by a client application often involves numerous inefficient steps, such as binding each row to a data object and loading the entire data set into memory before serializing it back to the caller. This type of approach has a negative impact on performance and scalabilty. Each row requires multiple heap allocations and constructor invocations, increasing latency and CPU load. Worse, the caller does not receive a response until the entire data set has been processed.

Further, since each response is loaded entirely into memory, high-volume applications require a large amount of RAM, and can only scale through the addition of more physical hardware. Eventually, the garbage collector has to run, slowing down the entire system.

A much more efficient approach is to stream response data. Instead of copying the query results into an in-memory data structure before sending the response, the web service can write a row of data to the output stream each time a row is read from the result set. This allows a client to begin receiving the data as soon as it is available, significantly reducing latency. Also, because no intermediate data structures are created, CPU and memory load is reduced, allowing each server to handle a higher number of concurrent requests. Finally, because fewer heap allocations are required, the garbage collector needs to run much less frequently, resulting in fewer system pauses.

Introducing HTTP-RPC

HTTP-RPC is an open-source framework for implementing REST services in Java. It is extremely lightweight and requires only a Java runtime environment and a servlet container. The entire framework is distributed as a single JAR file that is less than 30KB in size, making it an ideal choice for applications such as microservices where a minimal footprint is desired.

DispatcherServlet

HTTP-RPC’s DispatcherServlet type provides an abstract base class for REST-based web services. Service operations are defined by adding public methods to a concrete service implementation.

Methods are invoked by submitting an HTTP request for a path associated with a servlet instance. Arguments are provided either via the query string or in the request body, like an HTML form. DispatcherServlet converts the request parameters to the expected argument types, invokes the method, and writes the return value to the output stream as JSON.

The RequestMethod annotation is used to associate a service method with an HTTP verb such as GET or POST. The optional ResourcePath annotation can be used to associate the method with a specific path relative to the servlet. For example, the following class might be used to implement a web service that performs a simple addition operation:

@WebServlet(urlPatterns={"/math/*"})
public class MathServlet extends DispatcherServlet {
    @RequestMethod("GET")
    @ResourcePath("/sum")
    public double getSum(double a, double b) {
        return a + b;
    }
}

The following request would cause the method to be invoked, and the service would return the value 6 in response:

GET /math/sum?a=2&b=4

JSONEncoder

The JSONEncoder class, which is used internally by DispatcherServlet to serialize response data, converts return values to their JSON equivalents as follows:

  • CharSequence: string
  • Number: number
  • Boolean: true/false
  • Iterable: array
  • java.util.Map: object

Note that collection types are not required to support random access; iterability is sufficient. This is an important feature, as it allows service implementations to stream result data rather than buffering it in memory before it is written.

ResultSetAdapter

HTTP-RPC’s ResultSetAdapter class implements the Iterable interface and makes each row in a JDBC result set appear as an instance of Map, allowing query results to be efficiently serialized as an array of JSON objects.

For example, consider a web service that returns the result of a SQL query on this table, taken from the MySQL sample database:

CREATE TABLE pet (
    name VARCHAR(20),
    owner VARCHAR(20),
    species VARCHAR(20), 
    sex CHAR(1), 
    birth DATE, 
    death DATE
);

A method to retrieve a list of all pets belonging to a given owner might be implemented as shown below. Note that the example uses HTTP-RPC’s Parameters class to simplify query execution using named parameters rather than positional values. Also note that the method uses JSONEncoder to explicitly write the results to the output stream rather than simply returning the adapter instance, to ensure that the underlying result set is closed and system resources are not leaked:

@RequestMethod("GET")
public void getPets(String owner) throws SQLException, IOException {
    try (Connection connection = DriverManager.getConnection(DB_URL)) {
        Parameters parameters = Parameters.parse("SELECT name, species, sex, birth FROM pet WHERE owner = :owner");

        parameters.put("owner", owner);

        try (PreparedStatement statement = connection.prepareStatement(parameters.getSQL())) {
            parameters.apply(statement);

            try (ResultSet resultSet = statement.executeQuery()) {
                JSONEncoder jsonEncoder = new JSONEncoder();

                jsonEncoder.writeValue(new ResultSetAdapter(resultSet), getResponse().getOutputStream());
            }
        }
    } finally {
        getResponse().flushBuffer();
    }
}

A response produced by the method might look something like this, where each object in the array represents a row from the result set:

[
  {
    "name": "Claws",
    "species": "cat",
    "sex": "m",
    "birth": 763880400000
  },
  {
    "name": "Chirpy",
    "species": "bird",
    "sex": "f",
    "birth": 905486400000
  },
  {
    "name": "Whistler",
    "species": "bird",
    "sex": null,
    "birth": 881643600000
  }
]

With just a few lines of code, query results can be quickly and efficiently returned to the caller, with no intermediate buffering required.

More Information

This article introduced the HTTP-RPC framework and provided an example of how the ResultSetAdapter class can be used to efficiently transform JDBC query results into JSON. For more information, see the project README.

Simplifying Auto Layout in iOS with MarkupKit

Auto layout is an iOS feature that allows developers to create “responsive” user interfaces; i.e., applications that automatically adapt to device size, orientation, or content changes. An application built using auto layout generally has no hard-coded view positioning logic, but instead dynamically arranges user interface elements based on their preferred or “intrinsic” content sizes.

Auto layout in iOS is implemented primarily via layout constraints, which, while powerful, are not particularly convenient to work with. This article provides an overview of how constraints are typically managed in an iOS application, and then discusses some alternatives that can significantly simplify the task of working with auto layout.

Storyboards

The structure of an iOS user interface is commonly represented by XIB files or storyboards created using Xcode’s Interface Builder utility. This tool allows developers to lay out an application’s user interface visually using a drag and drop metaphor.

For example, the following is a storyboard representing a simple view. The view contains two subviews whose positions will be automatically determined at runtime based on layout constraints. The constraints pin the subviews to the edges of the parent view as well as to each other, with a 16-pixel gap in between:

Running the application in portrait mode on an iPhone 8 produces the following results:

In landscape, the application looks like this:

While storyboards are undoubtedly the most common way to create auto layout constraints in iOS, they are not necessarily the most efficient. Using Interface Builder to visually establish every relationship can be awkward, especially when working with large or complex view hierarchies. Further, storyboards are not stored in a human-readable text format, which makes it difficult to identify changes across revisions.

Programmatically Defined Constraints

In addition to storyboards, constraints can also be managed programmatically. For example, the following Swift code produces the same results as the previous example:

// Red view
let redView = UIView()

redView.translatesAutoresizingMaskIntoConstraints = false
redView.backgroundColor = UIColor(red: 202.0 / 255.0, green: 53.0 / 255.0, blue: 56.0 / 255.0, alpha: 1.0)

view.addSubview(redView)

// Blue view
let blueView = UIView()

blueView.translatesAutoresizingMaskIntoConstraints = false
blueView.backgroundColor = UIColor(red: 59.0 / 255.0, green: 85.0 / 255.0, blue: 162.0 / 255.0, alpha: 1.0)

view.addSubview(blueView)

// Constraints
NSLayoutConstraint.activate([
    NSLayoutConstraint(item: redView, attribute: .top, relatedBy: .equal,
        toItem: topLayoutGuide, attribute: .bottom,
        multiplier: 1.0, constant: 0.0),
    NSLayoutConstraint(item: redView, attribute: .bottom, relatedBy: .equal,
        toItem: bottomLayoutGuide, attribute: .top,
        multiplier: 1.0, constant: 0.0),
    NSLayoutConstraint(item: redView, attribute: .leading, relatedBy: .equal,
        toItem: view, attribute: .leadingMargin,
        multiplier: 1.0, constant: 0.0),

    NSLayoutConstraint(item: blueView, attribute: .top, relatedBy: .equal,
        toItem: topLayoutGuide, attribute: .bottom,
        multiplier: 1.0, constant: 0.0),
    NSLayoutConstraint(item: blueView, attribute: .bottom, relatedBy: .equal,
        toItem: bottomLayoutGuide, attribute: .top,
        multiplier: 1.0, constant: 0.0),
    NSLayoutConstraint(item: blueView, attribute: .leading, relatedBy: .equal,
        toItem: redView, attribute: .trailing,
        multiplier: 1.0, constant: 16.0),
    NSLayoutConstraint(item: blueView, attribute: .trailing, relatedBy: .equal,
        toItem: view, attribute: .trailingMargin,
        multiplier: 1.0, constant: 0.0),

    NSLayoutConstraint(item: redView, attribute: .width, relatedBy: .equal,
        toItem: blueView, attribute: .width,
        multiplier: 1.0, constant: 0.0),
])

Because the constraints are established in code, it is easy to identify changes between revisions. Unfortunately, managing constraints programmatically is not particularly convenient. This simple layout required the definition of eight individual constraints. More complex layouts could quickly become untenable.

Stack Views

The UIStackView class, introduced in iOS 9, provides an alternative to managing constraints directly. Stack views automatically arrange their subviews in a vertical or horizontal line, and can be nested to create sophisticated layouts.

For example, the following code uses a stack view to produce results identical to the first two examples:

let view = UIStackView()

view.isLayoutMarginsRelativeArrangement = true
view.spacing = 16

// Red view
let redView = UIView()

redView.backgroundColor = UIColor(red: 202.0 / 255.0, green: 53.0 / 255.0, blue: 56.0 / 255.0, alpha: 1.0)

view.addArrangedSubview(redView)

// Blue view
let blueView = UIView()

blueView.backgroundColor = UIColor(red: 59.0 / 255.0, green: 85.0 / 255.0, blue: 162.0 / 255.0, alpha: 1.0)

view.addArrangedSubview(blueView)

// Width constraint
NSLayoutConstraint.activate([
    NSLayoutConstraint(item: redView, attribute: .width, relatedBy: .equal,
        toItem: blueView, attribute: .width,
        multiplier: 1.0, constant: 0.0),
])

While it is arguably more readable than the previous version, this example is still somewhat verbose. It also still requires the explicit creation of a layout constraint to manage the width relationship, which is not ideal.

Introducing MarkupKit

MarkupKit is an open-source framework for simplifying development of native iOS and tvOS applications. Among other things, it provides a collection of UIView subclasses whose sole responsibility is managing the size and position of their respective subviews:

  • LMRowView – arranges subviews in a horizontal line
  • LMColumnView – arranges subviews in a vertical line
  • LMAnchorView – optionally anchors subviews to one or more edges

These classes use layout constraints internally, allowing developers to easily take advantage of auto layout while eliminating the need to manage constraints directly.

For example, the following code uses an instance of LMRowView to replicate the results produced by the previous examples. The weight property is used to ensure that the views are the same width. This property, which is added to UIView by MarkupKit, specifies the amount of excess space the view would like to be given within its parent view, relative to all other weights. Since both views are assigned a weight of 1, they will each be given 1 / (1 + 1), or one-half, of the available space:

let view = LMRowView()

view.spacing = 16

// Red view
let redView = UIView()

redView.backgroundColor = LMViewBuilder.colorValue("#CA3538")
redView.weight = 1.0

view.addArrangedSubview(redView)

// Blue view
let blueView = UIView()

blueView.backgroundColor = LMViewBuilder.colorValue("#3B55A2")
blueView.weight = 1.0

view.addArrangedSubview(blueView)

Like stack views, layout views are easy to work with programmatically. However, unlike stack views, no manual constraint is manipulation required; MarkupKit uses the weight values to automatically establish the width relationship.

Additionally, the colorValue(_:) method of MarkupKit’s LMViewBuilder class is used to simplify color assignment. The logic is still a bit verbose though, and could become difficult to manage as view complexity increases.

Markup

MarkupKit’s namesake feature is its support for declarative view construction. Using markup, the view hiearchy created in the previous example can be represented entirely as follows:

<LMRowView spacing="16">
    <UIView backgroundColor="#ca3538" weight="1"/>
    <UIView backgroundColor="#3b55a2" weight="1"/>
</LMRowView>

The markup is loaded using the view(withName:owner:root:) method of LMViewBuilder. This method is similar to the loadNibNamed(_:owner:options:) method of the NSBundle class, and returns the root element of the view hiearchy declared in the document:

view = LMViewBuilder.view(withName: "ViewController", owner: self, root: nil)

Unlike all of the preceding examples, this version is extremely concise. It is also much more readable: the element hierarchy declared in the document parallels the resulting view hiearchy, making it easy to understand the relationships between views.

For example, the periodic table shown below was constructed using a combination of MarkupKit’s layout views and UILabel instances:

Creating this view in Interface Builder would be an arduous task. Creating it programmatically would be even more difficult. However, in markup it is almost trivial. The complete source code for this example can be found here.

Summary

This article provided an overview of how layout constraints are typically managed in an iOS application and introduced MarkupKit, an open-source framework that can significantly simplify the task of working with auto layout in iOS.

For more information and additional examples, see the MarkupKit README.

MarkupKit 4.2 Released

MarkupKit 4.2 is now available for download. This release adds support for formatting the result of binding expressions.

In MarkupKit, attributes whose values begin with “$” represent data bindings. The text following the “$” character represents an expression to which the corresponding view property will be bound. Any time the value of the bound expression changes, the target property in the view will be automatically updated. MarkupKit monitors property changes using key-value observing, and evaluates expressions using the Foundation framework’s NSExpression class.

For example, a view controller might define a bindable property called name as follows:

class ViewController: UIViewController {
    @objc dynamic var name: String?

    ...
}

The following markup establishes a binding between the text property of a UILabel instance and the controller’s name property. Any changes to name will be automatically reflected in the label:

<UILabel text="$name"/>

Binding Expressions

Binding expressions are not limited to simple properties, however. For example, a custom table view cell might use an instance of the following class as a model:

class Row: NSObject, Decodable {
    @objc var heading: String?
    @objc var detail: String?
}

class CustomCell: LMTableViewCell {
    @objc dynamic var row: Row!

    ...
}

This markup binds the text property of the heading and detail views to the row’s heading and detail properties, respectively. Any time the value of row changes, the labels will be updated:

<LMColumnView>
    <UILabel text="$row.heading"/>
    <UILabel text="$row.detail"/>
</LMColumnView>

Expression Formatters

Binding expressions are also not limited to string values. For example, they can refer to numeric or date properties, and can even contain mathematical operations. It is not possible to bind the result of such expressions to string-based target properties directly. However, formatters can be used to convert a bound value to an appropriate textual representation.

Formatters are applied by appending a format specifier to a binding declaration. A format specifier contains the name of the formatter to apply, along with any associated arguments to the formatter. The format specification is separated from the actual expression by a double-colon (“::”).

For example, the following markup uses a date formatter to convert a person’s date of birth (represented by an NSDate instance) to a short-format date string:

<UILabel text="$person.dateOfBirth::date;dateStyle=short;timeStyle=none"/>

This markup converts a double value to a string using a maximum of three decimal places:

<UILabel text="$averageAge::number;numberStyle=decimal;maximumFractionDigits=3"/>

Formatters are obtained via the following method, which MarkupKit adds to UIResponder:

- (NSFormatter *)formatterWithName:(NSString *)name arguments:(NSDictionary *)arguments;

This method is called on the document’s owner any time the value of a bound, formatted expression changes. The default implementation provides support for “date” and “number” formatters, which correspond to the NSDateFormatter and NSNumberFormatter Foundation classes, respectively. The arguments represent the properties that will be set on the formatter to configure its behavior. Owning classes can override this method to support custom formatters.

Summary

Binding expressions in MarkupKit allow developers to declaratively establish relationships between a model object and a target view, enabling a simple and intuitive reactive development model. For more information, see the project README.

Project Marzipan?

Rumors have been swirling recently about “Project Marzipan”, an as-yet unreleased Apple project some originally thought might allow iOS apps to run on macOS. I’ve been personally hoping for something along the lines of UXKit, a private framework Apple reportedly used to develop the current version of Photos for the Mac.

However, based on a recent post by Daring Fireball’s John Gruber, it seems as though “Project Marzipan” (which is apparently now being called something else) may actually be a “declarative control API”:

The general idea is that rather than writing classic procedural code to, say, make a button, then configure the button, then position the button inside a view, you instead declare the button and its attributes using some other form. HTML is probably the most easily understood example. In HTML you don’t procedurally create elements like paragraphs, images, and tables — you declare them with tags and attributes in markup.

This sounds a lot like MarkupKit. It will be very interesting to see how this turns out when (or if) it is ultimately announced, which could apparently happen as early as WWDC 2018 but more likely 2019.

MarkupKit 4.1 Released

MarkupKit 4.1 is now available for download. This release adds support for live preview of markup documents within Xcode:

View classes tagged with IB_DESIGNABLE or @IBDesignable can now call the new preview:owner: method MarkupKit adds to the UIView class to validate markup changes at design time, avoiding the need to launch the iOS simulator. If an error occurs while loading the document, a label containing the error message will be overlaid on top of the view instance, allowing typos and other errors to be quickly identified.

For example, the following class provides a preview of the DetailViewController.xml document in the MarkupKit sample application. The view’s implementation of the prepareForInterfaceBuilder() method provides placeholder content; a temporary controller instance is used to ensure that outlets, actions, and bindings are handled properly:

@IBDesignable
class DetailViewControllerPreview: LMRootView {
    override func prepareForInterfaceBuilder() {
        let owner = DetailViewController(nibName: nil, bundle: nil)

        preview("DetailViewController", owner: owner)

        owner.iconImageView.image = UIImage(named: "BeachIcon", in: Bundle(for: type(of: self)), compatibleWith: nil)

        owner.headingLabel.text = "Heading Text"
        owner.detailLabel.text = "Detail Message"
    }
}

Note that this class is only used at design time – the view controller is still responsible for loading the view document at run time:

override func loadView() {
    view = LMViewBuilder.view(withName: "DetailViewController", owner: self, root: LMRootView())
}

Live preview can significantly reduce development time, since it eliminates the round trip through the simulator that is typically required to test an update. Using live preview, successive updates can be quickly verified, and the simulator launched only when the desired layout has been achieved.

For more information, see the project README.