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.

 

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.

Named Parameters in JDBC Queries

Prepared statements are a common way to execute parameterized queries in JDBC. For example, the following SQL might be used to retrieve a list of all users whose first or last name matches a particular character sequence:

SELECT * FROM user WHERE first_name LIKE ? or last_name LIKE ?

Parameter values are supplied at runtime via indexed setter methods defined by the PreparedStatement class:

statement.setString(1, pattern);
statement.setString(2, pattern);

This works fine for simple queries, but it becomes increasingly difficult to manage as the number of parameters grows. It is also redundant – although this query only requires a single argument, two parameter values must be supplied.

The Java Persistence API (JPA) provides a more convenient alternative using named parameters. For example, the above query might be written as follows in JPQL:

SELECT u FROM User u WHERE u.firstName LIKE :pattern or u.lastName LIKE :pattern

This is more readable and less verbose, as the caller only needs to provide the value of the “pattern” parameter once. It is also more resilient to changes, as the arguments are not dependent on ordinal position. Unfortunately, it requires a JPA-compliant object-relational mapping (ORM) framework such as Hibernate, a dependency that may not be satisfiable in all situations.

The org.httprpc.sql.Parameters class provided by the HTTP-RPC framework brings named parameter support to JDBC. The parse() method of this class is used to create a Parameters instance from a JPA-like SQL query; for example:

SELECT * FROM user WHERE first_name LIKE :pattern or last_name LIKE :pattern

It takes a string or reader containing the query text as an argument:

Parameters parameters = Parameters.parse(sqlReader);

The getSQL() method of the Parameters class returns the processed query in standard JDBC syntax. This value can be used in a call to Connection#prepareStatement():

PreparedStatement statement = connection.prepareStatement(parameters.getSQL());

Parameter values are specified via the put() method:

parameters.put("pattern", pattern);

The values are applied to the statement via the apply() method:

parameters.apply(statement);

Once applied, the query can be executed:

ResultSet resultSet = statement.executeQuery();

Note that Parameters is not limited to queries; it can also be used for updates.

A complete example using the Parameters class can be found here. It is a simple REST service that allows a caller to search a database of pets by owner name.

See the project README for more information.

JTemplate: Data-Driven Presentation Templates for Java

Templates are documents that describe an output format such as HTML, XML, or CSV. They allow the ultimate representation of a data structure to be specified independently of the data itself, promoting a clear separation of responsibility.

The CTemplate system defines a set of "markers" that are replaced with values supplied by the data structure (which CTemplate calls a "data dictionary") when a template is processed. In JTemplate, the data dictionary is provided by an instance of java.util.Map whose entries represent the values supplied by the dictionary.

For example, the contents of the following map might represent the result of some simple statistical calculations:

{
    "count": 3, 
    "sum": 9.0,
    "average": 3.0
}

A template for transforming this data into HTML is shown below:

<html>
<head>
    <title>Statistics</title>
</head>
<body>
    <p>Count: {{count}}</p>
    <p>Sum: {{sum}}</p>
    <p>Average: {{average}}</p> 
</body>
</html>

At execution time, the "count", "sum", and "average" markers are replaced by their corresponding values from the data dictionary, producing the following markup:

<html>
<head>
    <title>Statistics</title>
</head>
<body>
    <p>Count: 3</p>
    <p>Sum: 9.0</p>
    <p>Average: 3.0</p> 
</body>
</html>

TemplateEncoder Class

JTemplate provides the TemplateEncoder class for merging a template document with a data dictionary. Templates are applied using one of the following TemplateEncoder methods:

public void writeValue(Object value, OutputStream outputStream) { ... }
public void writeValue(Object value, OutputStream outputStream, Locale locale) { ... }
public void writeValue(Object value, Writer writer) { ... }
public void writeValue(Object value, Writer writer, Locale locale) { ... }

The first argument represents the value to write (i.e. the data dictionary), and the second the output destination. The optional third argument represents the locale for which the template will be applied. If unspecified, the default locale is used.

For example, the following code snippet applies a template named map.txt to the contents of a data dictionary whose values are specified by a hash map:

HashMap<String, Object> map = new HashMap<>();

map.put("a", "hello");
map.put("b", 123");
map.put("c", true);

TemplateEncoder encoder = new TemplateEncoder(getClass().getResource("map.txt"), "text/plain");

String result;
try (StringWriter writer = new StringWriter()) {
    encoder.writeValue(map, writer);

    result = writer.toString();
}

System.out.println(result);

If map.txt is defined as follows:

a = {{a}}, b = {{b}}, c = {{c}}

this code would produce the following output:

a = hello, b = 123, c = true

Additional Information

This article introduced the JTemplate framework and provided a brief overview of its key features.

The latest JTemplate release can be downloaded here. For more information, see the project README.