About a year ago I decided to change careers, somewhat. I was a full-stack engineer on Wayfair’s Room Planner project when I chose to try iOS development. Room Planner is a feature that lets users design their space by placing products in a virtual room. I joined the iOS side of the Room Planner team, so I was hopeful that the shared context would ease the transition. While it did to some degree, learning a new language (Swift) and a new codebase at the same time can be challenging, to say the least.
One way I thought I might fill my contribution gap would be to document and share my progress. At the beginning of the year I began a weekly internal newsletter, where I write about something interesting I learned that week. Some posts have been about Swift language quirks or gems. Others have been about Xcode or tooling. Some are in-depth discussions of internal libraries or techniques, while some simply highlight a keyboard shortcut I have found particularly useful recently.
My aim with this series was to highlight an opportunity for others to share an interesting quick-hit problem solving technique, language feature, or workflow improvement. While Lunch and Learns, talks, and trainings are important and valuable, I felt there was a gap that could be filled by regular, low-threshold content. Since I began publishing these newsletters, we’ve seen guest posts from various team members, as well as interest from our Android team in developing their own content.
Looking to spin up a series yourself? I’ll let you in on a secret. The content isn’t the point – the culture is. The concept of this series is to foster a culture of learning and knowledge sharing, much like the regular trainings, Lunch and Learns, and talks Wayfair invests in.
The newsletter series, called This Week I Learned (TWIL), has seen a post every week this year so far. We have decided to highlight several of our previous editions as we go along. Here is the first of many – happy reading!
TWIL: Enumerations with Associated Values, Computed Properties, and Failable Initializers
This week I learned some interesting things about Swift Enums. When first learning that Swift had Enumeration types, I made an assumption that I already understood them pretty well from having seen Union types (also called Sum types or OR types) in other languages. An enum defines a type that allows a member to be one of several other types. For example, a playing card is suited, so its suit is defined as one of the following possible values: Heart, Diamond, Spade, or Club. Pretty simple – or so I thought.
I was exploring enums in the context of Room Planner. For example: Think about items being placed in a room. If you load a previously-saved room, you might have some items already saved that have an
itemId appropriate for the database (Int64). If we then add an item to the room on the client from a catalog drawer, for example, what should its
itemId be? It hasn’t been saved to the database and so doesn’t have one. But we still want to be able to individually identify it by an ID even before we save it. And we might also want to be able to distinguish this type of unsaved item from a saved one.
Currently, on 2D Room Planner, we use negative itemIds to identify newly-added room items. This works but it isn’t as stable as it could be in a typed language and also leads to some awkward is-this-a-negative-id checks. Instead, for 3D Room Planner we could use an enum to distinguish between saved and unsaved room items. We could have two different types here; one to represent saved items and one to represent temporary items, with associated values for their IDs.
Here we have an ItemId enum that can be used to distinguish saved items, which have server-generated Int64 IDs, from temporary items, which have client-generated UUIDs. In order to create a new temp ID, we would just assign it with
ItemId.temp(UUID()). As mentioned before, we might need to distinguish between the two types of items. For example, we might want to do an incremental save of newly added items only to turn them from temporary items into saved ones. Enums make this easy by using a switch statement to value match.
In SceneKit (a rendering engine and API for 3D assets, which we use for 3D Room Planner), each node can be given a name. That name is a string and can be used to easily search through the hierarchy of nodes to find what we are looking for by using the function
childNode(withName: String, recursively: Bool). If we wanted to, we could set the items’ node names to string representations of their
ItemIds. It’s important to know that Int64 and UUID have different ways of getting their string representations and we could use a computed property on our enum to get the string value in a simple, consistent way.
Here we created a computed value called
stringValue that uses a switch statement to match which value type we have and then return the string value of that type.
And here it is in action:
We now see how we’re able to get a string value to use as a node’s name by using a computed property, but what about going in the other direction? How would we create a valid
ItemId from a string name?
The answer to the above is to use a failable initializer. We could try to initialize our
ItemId enum with a string. However, not every string would or should work. If we used a value of “abc” to try to initialize our enum, what should it do? What type of
ItemId would it be? It’s neither an Int64 or a UUID. This is the value that failable initializers provide. It will result in an optional
ItemId. If initialization fails, we get nil. Otherwise, we know we have either a saved (Int64)
ItemId or a temporary (UUID)
In the failable initializer we first try to cast the string value as an Int64; if that doesn’t work we try to create a UUID to see if the string is a valid UUID string value. Failing that, we return nil to signify that initialization has failed and that the optional should be nil.
Here’s how it would be used: