Recently, the iOS development circles have been pretty busy talking about functional programming concepts. One of those is using immutable values, rather than using mutable objects and sharing references to them. If you’re not completely familiar with the topic, Andy Matuschak gave a wonderful talk about Making Friends with Value Types where he very well explains the advantages of this approach.
It resonates with how we have been thinking about the model layer for our apps, and this was the final tipping point to start fully embracing immutability where possible. It is a significant paradigm shift and doesn’t come without caveats. This is not a bad thing, though – mutability has its own caveats as well (arguably worse ones in the long run), you just already learned how to handle those! Let’s explore some challenges you might face when dealing with immutable values in real life.
Regardless of whether you use Objective-C classes, Swift structs, or anything else to ensure immutability, chances are you will soon run into this question:
false, at least not in the code above. Swift won’t let you use the
== operator, unless you conform to the
Equatable protocol. Which in turn means you have to explicitly define the
jackAgain above are definitely equal, though, so let’s define
jack == jackAgain now returns
true. How about this case?
Given how we defined
== above, this is false. Did we define
== incorrectly for
Person, then? Are two
Persons equal if they have the same
socialSecurityNumber and the other values don’t matter? Documentation for
protocol Equatable has a clear opinion on this.
We can’t use
jackAfterAGoodMonth interchangeably, some of their values differ. For example
accountingDepartment.sendSalary(person) would yield different results if we pass
jackAfterAGoodMonth as an argument. We have defined
But you have to agree that although
jackAfterAGoodMonth aren’t equal, they refer to the same person. After all, they have the same social security number. That’s because
jackAfterAGoodMonth have the same identity, but they hold different values. Both are snapshots of Jack (or Jack’s identity, to be precise) at a certain point in time.
Are you me?
What if we need to know whether two snapshots represent the same identity? Perhaps a controller needs to react differently, if it stops displaying data about one person and switches to another.
The controller above (and whoever reads the code) has to have context on what defines the given identity, though. They have to understand how social security numbers work and that they’re unique. And they’ll have to learn a new concept of uniqueness for every new identity they’ll ever deal with.
I wonder if we can help here. To the abstraction machine!
Now the reader only needs to learn once what identity means and they’ll understand identity everywhere we use the concept of identity. It might seem trivial, but it’s a big win in terms of cognitive load.
By the way, how is
Inside our identifiable struct, we define what the unique identifier is, using an associated type. In our case, social security number is a
typealias IdentifierType to any type, though, so a
Person can be uniquely identified by a (strongly typed) pair of values, for example.
On a side note, it’d be nice if Swift allowed us to solve this using a generic protocol syntax. In an ideal world,
Person would conform to
It’s important to note that using a set to make sure we only have a single value for an identity won’t work. Uniqueness is ultimately enforced by equality in a set (hashes are just an implementation detail for speed).
Remember when we established that
jackAfterAGoodMonth aren’t equal? It also means that we can have both of them in a set.
Even if we went back and tried redefining
jackAfterAGoodMonth to be equal, inserting a new non-unique object into a set is a no-op. So we’d end up only with the original
jack in the set and Jack wouldn’t get a raise after all.
Latest and greatest
So we shouldn’t store an object in a
Set or an
NSSet for uniqueness. We could build our own collection,
IdentitySet<V: Identifiable>, which will enforce the uniqueness for us.
In practice, we’ll ideally never need this collection, though. It’s often just fine to pass values around in an array. Chances are, two different values for the same identity will never end up in the same array, as long as our persistence layer ensures the uniqueness. Let’s build such a persistent store, then.
So far, we have been dealing with immutable structs, and suddenly this is a mutable class. It makes sense, because persistence is inherently stateful.
Simple, but powerful API. We can update the previous value for the identity by calling
add, and make sure we have the latest value for an outdated one with
Real life persistence
For our last trick, we’ll switch the in-memory dictionary for a key-value store, which can provide persistence usable in a real app. Let’s store our values in
This requires a little dance, because as a generic Swift dictionary,
[V.IdentifierType:V]cannot be straight up stored to
NSUserDefaults. The details of the serialization are irrelevant to this article, so we’re not going to spend time on them. The functionality that becomes available in the end is much more interesting.
Now we can have multiple light-weight persistent stores, differentiated by a unique key, backed by
NSUserDefaults. The results they give us will never mutate (this is directly ensured by Swift’s value types, but the same can be achieved in Objective-C), and can be completely safely shared between threads.
We could also modify the identity store to point to a specific
NSUserDefaults suite, and share our data between apps and extensions!
You’d probably want more functionality from an identity store used in production. I hope I illustrated some basic challenges of using immutable values and got rid of the anxiety to make the leap and start using them.
If you find the article interesting, you can follow me on Twitter, I’m more than happy to keep the discussion going. We’re also hiring at ShopKeep – a growing team (~15 iOS developers), stacks in Go and Ruby and offices in NYC, Belfast and Portland. The code discussed above is available as a gist.