In the middle of developing my tvOS app, Scoreboard, I found myself tweeting ecstatically:

Playing with my first serious project with an all-value-type model, and it just keeps getting more stunningly elegant.

And then the next day, I added:

You guys. I can’t even deal with how all of your updating logic *vanishes* when your models are value types.

I thought I might explain these comments, because what I learned while writing Scoreboard will probably change how I write every app I ever make in the future.

What's Scoreboard?

Scoreboard is my new tvOS app. It's for keeping score when you're playing real-world games with real-world friends. It turns out that this is a really convenient thing to put on a TV where everyone can see it, instead of a piece of scrap paper that only one person has access to.

Scoreboard is written entirely in Swift, and except for the classes responsible for actually persisting data (to NSUserDefaults, because there's not much of it and CloudKit is a little ambitious for a 1.0), its model is all value types.

Scoreboard has a very simple model. Each user has n Scoreboards stored in an array, and each Scoreboard has n Players stored in an array. Barebones definitions of these types:

struct Scoreboard: Equatable {
    var name: String
    var players: [Player]
}

func == (lhs: Scoreboard, rhs: Scoreboard) -> Bool {
    return lhs.name == rhs.name && lhs.players == rhs.players
}

struct Player: Equatable {
    var name: String
    var score: Int
}

func == (lhs: Player, rhs: Player) -> Bool {
    return lhs.name == rhs.name && lhs.score == rhs.score
}

These could, of course, be classes, and in Objective-C they undoubtedly would have been. But making them structs instead has tremendous benefits—even in a very simple app like Scoreboard which currently uses no concurrency at all.

A Thousand Setters Blooming

The key is that, if you mutate something deep inside a value-typed data structure, then the setters for all of its parents are called. If you're in a ScoreboardListViewController with a var scoreboards: [Scoreboard], and you say self.scoreboards[0].players[0].score = 1, that's equivalent to saying all this:

var scoreboards = self.scoreboards
var scoreboard0 = scoreboards[0]
var players = scoreboard0.players
var player0 = players[0]

player0.score = 1
players[0] = player0
scoreboard0.players = players
scoreboards[0] = scoreboard0
self.scoreboards = scoreboards

Five different instances—the Player, the Array<Player>, the Scoreboard, the Array<Scoreboard>, and the ScoreboardListViewController—are all implicitly notified of that change by having their setters—and thus their willSet and didSet observers—called. Each one of them has access to both the old value and the new value, too. And that opens up all sorts of possibilities.

(Of course, swiftc's optimizer is quite clever, and if it can prove that there are no computed setters or observers, it'll optimize these setter calls into memory sets. That's not hard to prove for structs.)

Detecting Deep Model Changes

Typically when you implement a view controller, you update the model and then update the view to match. For instance, ScoreboardViewController (which shows the player names and scores in a particular Scoreboard) might have action methods like these:

@IBAction func addPlayer() {
    let newIndex = scoreboard.players.endIndex
    let newIndexPath = NSIndexPath(forRow: newIndex, inSection: 0)

    scoreboard.players.insert(Player(), atIndex: newIndex)
    collectionView?.insertItemsAtIndexPaths([newIndexPath])
}

@IBAction func incrementSelectedPlayerScore() {
    guard let indexPath = tableView.indexPathForSelectedRow else { return }
    let index = indexPath.row

    scoreboard.players[index].score++
    tableView.reloadItemsAtIndexPaths([indexPath])
}

Of course, these aren't the only places you'll need to update the table view. If a master view controller changes which model this detail controller is showing, or if something outside this view controller (like a change synced over CloudKit) alters a model, you need to detect that and update the view. And it's tricky to make sure that everything is animated appropriately.

But if your model is all value types, then any model change that affects this view controller will call its setter. So you can delete all that collectionView code and replace it with one line:

var scoreboard: Scoreboard {
    didSet {
        collectionView?.reloadData()
    }
}

That's not the best way to update the view, of course—ideally you want it to animate the changes. We'll explore an exceedingly clever way to do that in Part II.