A practical guide to building a TicTacToe game using Reactive Cocoa 4

This post is about my experience building a small project using Reactive Cocoa 4 and Swift 2. I am new to using FRP, but have always found it to be a very interesting topic. In my view, most of its appeal comes from the promise to eliminate great amounts of mutable state within an application and focus on the actual data flow. By focussing on data flow instead of intermediate data storage, it is possible to reduce the complexity of a project and increase maintainability and readability of the code itself.

„Where is truth in my application?“ Andy Matuschak
Advanced iOS Application and Architecture Patterns (WWDC 2014)

The code for the project can be found on my Github page.

Starting out with RAC 4

As someone being completely new to RAC, I had a hard time finding good resources that helped me getting started. RAC was initially developed for Objective-C and a lot of the resources floating around on the web still target this older version. When Swift came along, the authors completely updated the project and made it Swift only (RAC 3). However, when Apple released Swift 2 at WWDC 2015, RAC’s version number increased once more and RAC 4 has been started to benefit from the new features in Swift 2.

As mentioned, most of the introductory resources that can be found on the web deal with earlier versions of RAC. The Ray Wenderlich tutorial, while doing a great job in explaining the fundamental principles, still uses Objective-C. Colin Eberhardt gave a talk about Reactive Cocoa and Swift and outlined how the API changed with the adoption of Swift. He also published a number of articles explaining the fundamentals of RAC 3 which are generally great reads but don’t take your hand to stepwise guide your way into the world of RAC 4. Lastly, Agnes Vasarhelyi built a project using Swift and RAC and extensively blogged about it.

This post is an attempt to give a practical introduction to RAC and provide starting points for people new to the framework.

Integrating RAC 4 into your project

RAC can best be imported using Carthage. As RAC 4 is currently in Alpha, you need to explicitly add the version number in your cartfile:

github "ReactiveCocoa/ReactiveCocoa" "v4.0.0-alpha.4"

When your cartfile looks like this, run carthage update and let the dependency be resolved. Then you manually have to add the frameworks ReactiveCocoa.framework as well as Result.framework to your Xcode project.

frameworks

TicTacToe requirements

Let’s now quickly consider the UI elements and features that our TicTacToe app should have.

    • Two textfields where the players‘ names can be entered.
    • One button to start a new game. This button should only be enabled if there is at least one character in both textfields (so that we know that both players have names).
    • One label that displays the players‘ names while a game is active.
    • One label that indicates whose turn it is.
    • One label that displays the winner of the game as soon as there is one.
    • A 3×3 grid that represents the board of the game.
    • When a (free) cell in the grid is tapped, the marker of the current player appears in that cell.
    • Taps on already marked cells are ignored.
    • All taps on the grid should be ignored as soon as one player won the game.
An overview of the required UI elements

An overview of the required UI elements

The Board and Game data types and APIs

According to the outlined requirements, I have constructed the following basic data types and API:

Board.swift

typealias Grid = [[Field]]

enum Marker {
    case Circle
    case Cross
}

enum Field {
    case Empty
    case Marked(Marker)
}

struct Board {
    let grid: Grid

    // returns the marker of the player with the next turn (nil if no turns left)
    var playersTurn: Marker?

    // checks if one of the players won the game (nil if no winner yet or board full)
    var winner: Marker?

    // checks if it is currently possible to make a move (false if a player won the game)
    var canMakeMove: Bool
}

Game.swift

struct Player {
    let name: String
    let marker: Marker
}

struct Game {
    let board: Board
    let players: (Player, Player)
}

A GridView to represent the Board

To give you an idea, how the board can be represented as a UI element, here is a short outline of the GridView I am using:

GridView.swift

// convenience type to access a position in the grid
typealias Position = (Int, Int) // (row, col)

class GridView : UIView {
    var gridViewCells: [GridViewCell] = []

    // change the cell at the target position to the image of the passed marker
    func updateCell(atPosition position: Position, withMarker marker: Marker)

    // set all cells of the grid to initial image
    func clear()
}

class GridViewCell: UIImageView {
    let position: Position
}

Using MVVM to represent the UI state

Next to RAC, I also wanted to use some MVVM in this project as the two are going very well together. MVVM, much like MVC, is a design pattern that helps to structure the code in your application. For MVVM in particular, the whole state of the application’s views gets represented by so called view model objects. The goal of this approach is to pull out code from the controller and move it into the view model objects to prevent ending up with Massive View Controllers. MVVM really shines when we have the option to create bindings between the properties of the view models and the UI elements instead of updating the UI manually whenever a change in the model occurs.

In our case, this means that the strings that are displayed on the labels should be properties of the view model object that represents the state of our view. Further, the property that decides whether we can still make moves by tapping empty cells in the grid is also part of the view model.

struct ViewModel {
    // strings to be displayed on labels        
    let whosTurn: MutableProperty<String> = MutableProperty("No active game")
    let names: MutableProperty<String> = MutableProperty("Please enter names of players")
    let winner: MutableProperty<String> = MutableProperty("No winner yet")

    // determines whether the grid view is enabled
    let canMakeMove: MutableProperty<Bool> = MutableProperty(false)

    // update the properties for a new game
    func newGame(game: Game)
  } 

Note that we don’t have a property in the view model that represents the state of our GridView. This is one defect in the current version of the app that should be fixed in a future version when I’ve figured out a good way to create a binding.

Implementation of the UI

At this point, we have the basic setup for the game and can start worrying about the implementation of the UI. At first, I have created a ViewController that contains all the mentioned UI elements.

The „traditional“ way

At this point, I want to take a quick step back and think about how we would approach the implementation using traditional, more imperative techniques. As an example, let’s try to figure out how to make sure the button is only enabled when both name fields are not empty. Two approaches immediately come to mind:

  • 1) Using KVO and be notified whenever the text in the text fields changes
  • 2) Using delegates and e.g. implement shouldChangeCharactersInRange from UITextFieldDelegate

This way, on any change of one of the textfields‘ contents, we can simply inspect the new contents and decide whether we want to enable or disable the button. This could look like this:

// setup KVO (e.g. in viewDidLoad)
self.name1TextField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
self.name2TextField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)

// implement handler
func textFieldDidChange(textField: UITextField) {
  let buttonEnabled = self.name1TextField.text.characters.count > 0 
      && self.name2TextField.text.characters.count > 0 
  self.startButton.enabled = buttonEnabled
}

So, what’s „wrong“ with this approach and how can we improve it using FRP? In the above implementation, we get notified every time the handler changes and need to update the enabled property of the startButton manually. Manually here means that we are using an assignment statement to update it, that is, we’re saying how we want to update it explicitly.

Wouldn’t it be nicer to just specify the *general fact that this button should be enabled whenever the textfields are not empty to decrease cognitive load and computational overhead?* Let’s see how we can do this using RAC.

Using Signals to control the data flow in the application

We’re now finally getting to the meat of Reactive Cocoa. The largest benefit that this framework provides is that it creates one, single interface for all sorts of events that you’d expect to be happening within your app. As iOS developers, we’re used to handling a large number of events, each in their own particular way. Two examples of such events I have already mentioned, KVO and delegation. More examples include target/action, networking callbacks or any other kind of asynchronous computation. Without RAC, all these events have to be treated differently, that is they require different kinds of handler functions that spread out across the whole project. It is easy to imagine that a more sophisticated UI and the need to handle all the events that might occur vastly increase the complexity in the code.

The solutions that RAC has for this are called Event and Signal. To get a better understanding of the two, here is my take on an (incomplete but hopefully intuitive) definition:

Events

An Event<T> is a value of type T at a specific point in time. I liked the explanation of this concept that was given in the docs of a Haskell GUI library, picturing an Event<T> as a tuple of type (time, T).

In that sense, an Event<String> could be emitted by a UITextField whenever its content changes. On every change, a new Event<String> is created and emitted that carries the current text.

In RAC, it is easy to access the value of any event object by calling event.value.

Signals

A Signal<T> is a stream of Event<T> objects, where a stream can simply be seen as an (infinite) list. With this notion of a list, it is legit (though not 100% accurate) to picture a Signal<T> to be of type [Event<T>]. Signals represent the common interface to events that I had mentioned above. Whenever you’re interested in some data within your application, you can create a Signal that carries this data and subsequently transform it until you have it in the form that you require.

Thinking about data flow

With the notions of Events and Signals, let’s think about how these constructs can be used to describe what’s happening in our TicTacToe app.

Looking again at the requirement to enable / disable the start button according to the number of characters in the two name fields, we want to avoid an explicit update of the button’s enabled property each time our condition is met. Rather, as mentioned above, we’d like to somehow express on a higher-level that it should be set to true only if both fields are not empty.

Let’s now see how we can achieve this in RAC 4. The magic provided by RAC for this scenario is the ability to bind properties to Signals. RAC uses a custom operator for these bindings which looks like this: <~. It is overloaded to account for three different scenarios:

  • 1) bind a mutable property of type T to a Signal<T>
  • 2) bind a mutable property of type T to a SignalProducer<T>
  • 3) bind a mutable property of type T to the value of another mutable property of type T

So, bringing together what we know about bindings and Signals, we need to execute the following steps to implement the enable/disable functionality of our button:

  • 1) We need to create Signals from our name fields to capture events whenever their content changes
  • 2) We need to transform the Signals from their original type String into a Signal with type Bool that only indicates whether the field is empty or not
  • 3) We need to combine the Signals from the two name fields to result into one Signal that represents whether both are fields are not empty
  • 4) Finally, we need to bind this combined Signal to the enabled property of our button

The first two steps can be achieved using the following code:

// construct signals that carry a Bool and indicate whether the fields are empty
let name1Signal = self.name1TextField.rac_textSignal() // RACSignal
    .toSignalAssumingHot()                             // Signal<AnyObject?>
    .assumeNoErrors()                                  // Signal<AnyObject?>
    .map { text in text as! String }                   // Signal<String>
    .map { $0.characters.count > 0 }                   // Signal<Bool>
let name2Signal = self.name2TextField.rac_textSignal() 
    .toSignalAssumingHot()
    .assumeNoErrors()
    .map { text in text as! String }
    .map { $0.characters.count > 0 }

rac_textSignal() is a function defined in RAC as an extension of UITextField. It returns an object of type RACSignal. As a quick side-note, if you’re confused about why this is a RACSignal rather than a Signal<T>, I share your confusion. The way I understand it is that RACSignal is still old baggage from older RAC versions (<= 2). At that time, there was a distinction between hot and cold signals. In RAC 3 these have been renamed and hot signals are now represented by Signal<T> whereas cold signals are called SignalProducer<T>.

Anyway, the way we deal with this RACSignal is by converting it to an actual Signal<T> that we can use in the RAC 4 API. We do this by calling toSignalAssumingHot(). Note that this is a convenience function which I took from a gist from Nacho Soto. The same goes for assumeNoErrors() which is optimistically called on the Signal right after we transformed it from a RACSignal to a Signal<AnyObject?>. Essentially, both calls are necessary to obtain the new Signal type from a RACSignal. At this point however, the Signal is still untyped, or more precisely, it’s of type Signal<AnyObject?>. In order to convert it to a Signal<String> and finally Signal<Bool> which can be bound to the enabled property of the button, we need to perform further operations. That’s why we first call .map { text in text as! String } to convert it to a Signal<String> and then .map { $0.characters.count > 0 } to create Signal<Bool> that only emits true when there is at least one character in the textfield. Of course, both calls to map could be put together into a single call but I wanted to make the conversion explicit here.

So, now we understand how to create and transform signals from our textfields. But right now we have two individual signals, one for each name field. The condition to enable the button however depends on both text fields, that’s why the next step consists of combining these signals.

To do so, we can simply use the function combineLatestWith(). Note that this function represents a specific strategy for combining Signals, more on that can be found here.

let combinedSignal = name1Signal.combineLatestWith(name2Signal)
    .map { tuple in // (Bool, Bool)
        tuple.0 && tuple.1
}

The return value from calling name1Signal.combineLatestWith(name2Signal) is another Signal that carries a tuple whose types are the types of the Signals that were involved in the combination. So, in our case the tuple is simply of type (Bool, Bool). With this knowledge, we can apply another transformation that takes care of merging the two Bools into one.

We now finally are where we want to be, i.e. we have one Signal<Bool> that emits true only if both name fields contain at least one character! What’s left is binding this Signal to our button’s enabled property.

We know that bindings can be implemented using the <~ operator. However, we can’t just do self.startButton.enabled <~ combinedSignal. The reason for this can be found in the type signature of <~ which looks like this:

public func <~<P : MutablePropertyType>(property: P, signal: ReactiveCocoa.Signal<P.Value, ReactiveCocoa.NoError>) -> Disposable

It looks a bit cryptic, but the important part is that it takes as parameters a MutablePropertyType and (which is essentially a typed MutableProperty) and a Signal of the same type as the property. So, logically what’s left to do is boxing the button’s enabled property into a MutableProperty. A great help for these kinds of tasks can be found in a gist by Colin Eberhardt who came up with a way to quickly wrap properties of UIKit elements into RAC’s mutable properties. I extended his gist by adding wrappers for the properties I required. In the case of the button’s enabled property, the extension looks like this:

extension UIButton {
    public var rac_enabled: MutableProperty<Bool> {
        return lazyMutableProperty(self, key: &AssociationKey.text, setter: { self.enabled = $0 }, getter: { self.enabled })
    }
}

With this, we can finally perform our binding:

self.startButton.rac_enabled <~ combinedSignal

MVVM and property bindings

Let’s now take a look at the MVVM properties and how these can be connected to our UI elements. I mentioned a bit earlier that the <~ operator can be used for three scenarios. This time, we’re actually dealing with a simpler case than with the button before. That’s because now we don’t have to create a Signal carrying any data, but instead we bind the UI elements directly to the view model’s properties (scenario #3). As a consequence, every time when a property in the view model changes, the corresponding UI element gets updated automatically.

The only thing that we have to take care of is again wrapping the properties of the UI elements into a MutableProperty as used by RAC. Using convenience functions from Colin Eberhardt’s gist, this can be done as follows:

self.namesLabel.rac_text <~ self.viewModel.names
self.whosTurnLabel.rac_text <~ self.viewModel.whosTurn
self.winnerLabel.rac_text <~ self.viewModel.winner

Now, whenever we update names, whosTurn or winner on our viewModel, the corresponding label is going to show the new content without us doing any more work.

Additionally, we add

self.gridView.value.rac_userInteractionEnabled <~ self.viewModel.canMakeMove

to make sure that the gridView ignores taps whenever the canMakeMove property of your viewModel is false. Now, we only need to take care of updating the viewModel’s properties and the UI will update itself.

Creating custom Signals from taps on the GridView

I have noted before that Signals can be created for any kind of event. We’re now getting to a more interesting case where we have to deal with custom events that are created by taps on our grid.

Let’s start by asking what the most interesting piece of information is that we’d like to know when a user taps a cell in our grid? Well, in our case what we’d like to know is the Position of the tapped cell! Note that Position is a typealias for (Int, Int) representing the row and column to identify a certain cell.

What we need is a Signal<Position> that emits a new event whenever a user taps on the gridView so that we know which cell has been tapped. We can then observe this Signal and perform the appropriate actions (e.g. placing the player’s marker or showing an error message if the move was illegal) when a new event occurs.

Creating the custom Signal can be done in the following way:

func createTapSignal() -> Signal<Position, NoError>? {
    var signals: [Signal<Position, NoError>] = []
    for maybeTap in self.gridView.value.cellsTapGestureRecognizers() {
        if let tap = maybeTap {
            let signal = tap.gestureSignalView() // Signal<UIView>
                .map { $0 as! GridViewCell }     // Signal<GridViewCell>
                .map { $0.position }             // Signal<Position>
            signals.append(signal)
        }
    }
    return signals.count > 0 ? Signal.merge(signals) : nil
}

What happens in this function is that we’re collecting the UITapGestureRecognizers that are associated with our GridViewCells (one for each cell). We then create Signals for all of them by calling gestureSignalView() and mapping the resulting Signal<UIView> to retrieve the Position from the GridViewCell, which finally results in a Signal<Position> which we collect in an array called signals. As a last step, we call Signal.merge(signals) which combines all the signals in the array to result in a single Single<Position> which we return. If we weren’t calling merge, we would have had nine different Signals that each return their position, but using merge we can create one Signal that only emits the position of the most recently tapped cell.

Finally, we need to observe the emitted Signals, this can be done using the following code (e.g. in viewDidLoad()):

// observe taps on the grid
if let taps = createTapSignal() {
    taps.observe { event in
        if let position = event.value {
            self.playerTappedCell(position)
        }
    }
}

We’re passing an anonymous callback function to observe, this function takes as input argument the emitted event (a user’s tap on a cell) and allows us to retrieve the position of the tapped cell which we can then pass to playerTappedCell().

Updating the Board

The only thing that we haven’t looked in detail yet was how we’re actually updating the Board and the GridView as the corresponding UI element. In playerTappedCell(), we’re using the following code:

if let currentPlayer = game.board.playersTurn {
    let boardOrMsg = makeMove(game.board, marker: currentPlayer, choice: position)
    switch boardOrMsg {
        case .Error(let msg): displayErrorMsgForInvalidMove(msg)
        case .Success(let newBoard):
            self.gridView.value.updateCell(atPosition: position, withMarker: currentPlayer)
            updateGame(newBoard)
    }
}

Note that makeMove() returns something of type BoardOrMsg which is the typealias for an enum that has a Success(Board) and an Error(String) case. The Error case is returned if the attempted move was illegal, along with a message describing the error. If the move is legal, the function will return the Success case and the new Board. With this new board, we’re able to update the game state as well as the UI elements. We set self.viewModel.whosTurn to the player with the next turn and/or update self.viewModel.winner, if one of the players won the game. At this point, the only thing that isn’t very FRP-like is the way we update the gridView. Right now, it is updated manually once we received the new board. It would be nice to have a binding here so that we can avoid the explicit call to updateCell().

Leave a Reply

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax


*