A Declarative Architecture for Swift Apps

Update: I’ve written a new blog post describing how I’ve improved upon this architecture.

I’ve long been interested in seeking ways to design software in a declarative way. That’s why I’ve lately been very interested in tools like Om Next that provide good patterns for managing the state of your entire application and rendering UI based off that state.

I was recently assigned to a new iOS project using Swift, which was a great opportunity to to learn Apple’s new language and see if I could leverage it to bring a greater level of declarativeness to iOS programming.

State

My first concern was to ensure we have good semantics for storing, interpreting, and modifying state in our application. In particular, I wanted:

  • Some kind of immutable data structure that I could store centrally
  • The ability to change our state by providing a function that would derive our next state
  • A declarative way to project information from our state so we don’t need to store superfluous information
  • A way to observe when state changes, so we can, e.g., update the UI

Immutability in Swift

Swift’s structs immediately seemed useful here, as you can define your fields using let instead of var, and their values can’t be mutated. This even applies to complex “values” such as dictionaries. Unfortunately, this leads to difficulty in deriving new structs. You must manually create a new struct that references all the values that did not change, along with the new values for fields that did change. It gets even worse when you have nested structures.

Thankfully, the whole point of structs in Swift is that they have value semantics. They are not a reference type. When you return a struct from a function, or accept it as an argument, you get a copy of it. We can leverage that by gating access to our state through functions to protect it from mutating unexpectedly, while still defining our fields as vars to make our lives easier.

Note that this has a particular caveat: If you store a mutable reference in the state struct, e.g., a dictionary, I think it can still be mutated from underneath you. I would suggest using let for those types of data, or using more structs instead.

Changing Our State

The goal of this is to be able to define functions that derive new states, touching only what’s needed. Let’s say our state looks like this:


struct State {
  var authorization : Authorization = .Unauthorized(.Unstarted)
  ...
}

We could define a function that encapsulates what it means to transition to a state where the user is authorized:


func authorized(username: String, token: String)(var state: State) -> State {
  state.authorization = .Authorized(Auth(token: toke, username: username))
  return state
}

We could then use this function, like:


// When we receive success from our server
appState.change(authorized(username, token: token))

This turns out to be pretty powerful. We’ve separated the “what” (the fact that we’ve now got authorization) from the “how” (update specific fields, use specific data structures). We’ve also gained the ability to compose these semantics together using simple function composition. And, as it turns out, it’s very easy to define your own custom operator to do function composition.

Projecting Our State

Above, we defined a state mutation as a function from a state to another state. We can also define functions that interpret the information we capture in our state, as a function of a state to anything. For example, we might have:


func hasAuthorization(s: State) -> Bool {
  switch s.authorization {
  case .Authorized:
    return true
  default:
    return false
  }
}

And, as before, we’ve successfully separated the “what” from “how.” Accessing any information happens via passing a function to our app state:


//somewhere else
guard appState.current(hasAuthorization) else {
  return
}

In general, we try to only store our raw information in our state. Naturally, what’s important depends on the the domain of your application. If you want to show a spinner while you’re making a request to your server, then you’ll need to find a way to model that in the structure of your application’s state type, and provide a function to project that information out.

Observing Our State

Of course, this is all useless without a way to declaratively wire this
information up to our user interface. I essentially needed a way to bind the various attributes of our UI widgets to projections of our app’s state. For this, I turned to RxSwift.

Above, I’d referred to appState. We have an appState class in our application that holds a private reference to our current state. Internally, I stored that state in an RxSwift variable. This provides us with the ability to create an RxSwift observable of our state as it changes over time. Just as we have public functions of this class that allow you to change or interpret the state, we also have a function to observe projections over time.

This allows us to use our viewDidLoad hooks of our view controllers to easily tie this information to our widgets:


override func viewDidLoad() {
  super.viewDidLoad()
  let busy = appState.observe(busyAuthorizing)
  let disable = busy.map(!)
  busy.bindTo(spinner.rx_animating).addDisposableTo(bag)
  disable.bindTo(spinner.rx_hidden).addDisposableTo(bag)
}

Just like that, we have an extremely declarative way to ensure that our UI is always reflecting the true state of our application.

Conclusion

This approach is far from perfect, but it’s been paying off very nicely as we develop our app. Swift has some deficiencies, but it’s quite capable of being leveraged in a functional style without much pain. Swift is also the first strongly typed language that I’ve used for anything significant, and it’s been a great experience to see what tradeoffs that provides.

Conversation
  • marc says:

    This code will fail under Swift 3.0, it uses curried functions + var parameter:

    “`
    func authorized(username: String, token: String)(var state: State) -> State {
    “`

    • Chris Farber Chris Farber says:

      Thanks, Marc – I’m aware of this. I actually wrote this post before Swift 2.2 was released and started warning about these.

      I’m planning to write a future blog post about a revised way of implementing this architecture that doesn’t fight against the language as much.

  • Curious if you came across ReSwift (https://github.com/ReSwift/ReSwift) at all when building this out. From its readme, “ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.”

    I almost went with RxSwift also but turned away from it for the moment. Great post btw :)

  • Janek Spaderna says:

    Hi, nearly everything inside the Swift stdlib has value semantics, including arrays and dictionaries.
    It does this mostly leveraging copy-on-write, but this is an implementation detail you don’t have to worry about.
    Here’s an example:

    struct S { var dict: [Int: String] }
    var someDict = [1: “Hello”]
    var someS = S(dict: someDict)
    someDict[2] = “World”
    print(someS.dict.count) // prints 1
    someS.dict[1] = nil
    print(someDict.count) // prints 2

  • Comments are closed.