Swift Builders

Similar to languages such as Groovy, Swift has special syntax support for lists (Array) and maps (Dictionary) which results in a compact representation of what’s being defined, inline with the code that is using it. e.g.

println([1,2,3,4,5].count) //Output: 5


var x = 0
[1,2,3,4,5].each { x += $0 }


extension Array {
  func each (block: (T) -> ()) {
    for item in self { block(item) }

Tree Based Syntax

In this article, we extend the concept of syntactic support to defining (somewhat) arbitrary nested tree structures.

Let’s say we want to programmatically generate HTML, an imperative solution for which likely requires lengthy code with templates engines / variable substitution, interspaced with loops and branching, and associated glue code.

Syntactic builders can offer an elegant alternative:

html {
  head {
    title( "Page Title" ) {}

  body(["class":"bodyClass"]) {
    for i in 0..3 {
      div(["id":"div_(i)"]) {
        p("Click ") {
             "link (i+1)") {}

The above example is valid Swift code and generates the following HTML code:

      <title>Page Title
    <body "class"="bodyClass">
      <div "id"="div_0">
          <a "href"="http://www.example.com/link0.html">link 1
      <div "id"="div_1">
          <a "href"="http://www.example.com/link1.html">link 2
      <div "id"="div_2">
          <a "href"="http://www.example.com/link2.html">link 3

Builder Methods

The builder operates by invoking several custom methods on a underlying Builder class where method names parallel html tag names. e.g. the div tag:

func div(block: ()->()) { div(nil, nil, block) }
func div(attr: Builder.Attributes?, block: ()->()) { div(attr, nil, block) }
func div(value: AnyObject?, block: ()->()) { div(nil, value, block) }
func div(attr: Builder.Attributes?, value: AnyObject?, block: ()->()) { Builder.node("div", attr: attr, value: value, block) }

All builder methods follow the same syntax and accept

  • an optional map of attribute name/value pairs
  • an optional tag value and
  • a closure for configuring the tag.

Internally, the Builder creates a tree of Node objects which parallels the Swift tree.

class Node {
    var name : String
    var parent: Node? { didSet { level = parent!.level + 1} }
    var children = Node[]()
    var attr = Attributes()
    var block: Block?
    var level = 0
    var value : AnyObject?

    init( name: String) {
      self.name = name

    func visit(before: Visitor?, after: Visitor?) {
      if let v = before { v(self) }
      for c in children { c.visit( before, after ) }
      if let v = after { v(self) }

Traversing with Visitors

A visitor method on the Builder allows for customized traversal of the object tree:

func visit(before: Builder.Visitor?, after: Builder.Visitor?  = nil) {
  Builder.visit(before, after)

visit accepts two closures which are applied before and after visiting a node’s children, respectively. Here is a simplified visitor for generating HTML markup:

visit( {
  let node = $0
  let r = repeat(" ", node.level*2)
  var tag = "(r)<(node.name)"
  for a in node.attr { tag += " "(a.0)"="(a.1)"" }
  tag += ">"
  if let v:AnyObject = node.value { tag += "(v)" }
    let node = $0
    let r = repeat(" ", node.level*2)
    var tag = "(r)</(node.name)>"
  } )


  1. Swift closures aren’t dynamic in that variables and methods in a closure are bound at compile time. This is in contrast with dynamic closures in Groovy for example, where methods calls within closures are’t checked at compile time and an exception can be thrown at runtime. Further, references to instance members are required to be explicit with a self. prefix, which only adds to verbosity.

  2. In this example, tree node methods such as div are kept in the global namespace to avoid self. prefix clutter.


The full source listing can be found here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s