Monthly Check-Ins

Two days I look forward to each month are the 28th and the 30th1. A couple of my closest friends, Ashoka and Shiva, and I have established a ritual of checking in with one another on these days. The setup is simple. An hour-long, monthly calendar event where we work through problems we’re facing, wins for the past month, common themes in our journals, and big questions we’d like to bounce off one another.

The only regret I have about these checkins is not starting sooner. Despite being on opposite coasts, they have allowed us to dive deep into one another’s lives and skip basic questions like “how’re you doing?”, “work going well?”, and “dating anyone?” that often kickoff the rare times we meet in-person.

In a recent checkin, Shiva and I traded ideas on how to prioritize deep work, why first principles are often better than analogies, long-term perspectives on our careers, and how to wake up earlier. Moreover, the discussion often permeates our always-on conversations in the form of motivational messages before interviews or on some idle Tuesday evening.

This is one of the habits that has kept me happy. Shiva and Ashoka, here’s to the next 1000 checkins!

If you’re reading this post and want to try monthly check-ins, let’s make it happen! 🚀

1: Adjusting on Februarys of course

Case Closed: A Dive into Open Enumerations

Time for a pop quiz. Given that NSFetchedResultsChangeType is defined as follows:

typedef NS_ENUM(NSUInteger, NSFetchedResultsChangeType) {
	NSFetchedResultsChangeInsert = 1,
	NSFetchedResultsChangeDelete = 2,
	NSFetchedResultsChangeMove = 3,
	NSFetchedResultsChangeUpdate = 4

What is the value of changeType in the following expression (assuming Swift 3.x)?

let changeType = NSFetchedResultsChangeType(rawValue: 5)

Hint: Enumerations with raw values in Swift implicitly conform to RawRepresentable (⇒ the presence of init?(rawValue:)).

(Corgi for padding between question and answer)

A photo posted by Zoey Corgella the corgi (@zoeydacorgi) on

Turns out changeType is non-nil, works in a switch statement, and has undefined behavior!

To understand why this is the case1, we have to take a step back and define two terms: open and closed enumerations. NSFetchedResultsChangeType is an NS_ENUM and when bridged to Swift, it’s represented as an “open” enumeration. The distinction between open and closed enumerations can be found in Jordan Rose’s manifesto on Swift Library Evolution. In summary, open enumerations will be the default once ABI resilience ships and allow the following changes between library versions without breaking binary compatibility:

  • Adding a new case2
  • Reordering existing cases is a binary-compatible source-breaking change. In particular, if an enum is RawRepresentable, changing the raw representations of cases may break existing clients who use them for serialization
  • Adding a raw type to an enum that does not have one
  • Removing a non-public, non-versioned case3
  • Adding any other members
  • Removing any non-public, non-versioned members
  • Adding a new protocol conformance (with proper availability annotations)
  • Removing conformances to non-public protocols

On the flip side, closed enumarations must be explicitly marked with the @closed attribute. This attribute prevents the enumeration from “having any cases with less access than the enum itself3 and adding new cases in the future.” In addition to guaranteeing case exhaustion for clients, the attribute implies:

  • Adding new cases is not permitted
  • Reordering existing cases is not permitted.
  • Adding a raw type to an enum that does not have one is still permitted.
  • Removing a non-public case is not applicable.
  • Adding any other members is still permitted.
  • Removing any non-public, non-versioned members is still permitted.
  • Adding a new protocol conformance is still permitted.
  • Removing conformances to non-public protocols is still permitted.

Despite “open” being the default for enumerations moving forward, adding new cases (and consequences of this) can lead to gnarly bugs like the one my teammate, Paul, and I found earlier this week.

Invalid Changes Sent to NSFetchedResultsControllerDelegate

NSFetchedResultsControllerDelegate provides an optional function to notify concrete implementations that a fetched object has been changed due to an add, remove, move, or update. During testing, we noticed “extra” calls4 to this function, which caused crashes via subsequent invalid table view updates. To set some context, our concrete implementation had the following structure:

public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, 
               didChange anObject: AnyObject, 
                      at indexPath: IndexPath?, 
                     for type: NSFetchedResultsChangeType, 
            newIndexPath: IndexPath?) {

	/* Precondition checks */

	// ...

	switch type {
	case .insert:
	/* Insertion logic */
	case .delete:
	/* Deletion logic */
	case .move:
	/* Move logic */
	case .update:
	/* Update logic */

Breakpointing on calls to this function, we noticed something odd in the debug menu:

Looks like Core Data is making calls to this delegate function with invalid NSFetchedResultsChangeTypes! However, due to the nature of open enumerations, we have no way of disambiguating this from a scenario where a new case is added (i.e. NSFetchedResultsChangeType(rawValue: x) != nil, ∀x ∈ {UInt(0), UInt(1), UInt(2), ... , UInt.max}, due to the lack of guaranteed case exhaustion). To make this even trickier to find, the example case seemed to match against the first case, .insert, (as Caleb noted in his testing as well). But, this behavior is technically undefined.

Getting Around This

To safeguard against this bug until a default case is required for open enumerations, we came up with an imperfect workaround. In the delegate function body, we added a guard to ensure the presence of a valid raw value on the type parameter:

guard [
	].contains(type.rawValue) else {

	/* Note about the open enumeration issue we've described and logging to track this in production */

This keeps our code crash-free until ABI resilience alleviates the need for the guard. Of course, this doesn’t handle the scenario in which Apple adds a new case, but that’s why we perform logging in the else clause to keep an eye on any API updates. You can watch this issue on Swift’s JIRA.

Hope this helps make open and closed enumerations more clear! Huge shoutout to Joe Groff, Caleb Davenport, and Jordan Rose for discussing this topic in the open1 on Twitter and in the Swift repository docs.

1: Pun intended 😁

2: We’ll soon see why this feature causes changeType in the quiz to be non-nil.

3: Non-public cases don’t exist at the moment.

4: Masked as insertions due to this undefined behavior.

Nostalgia and Upper Bounds

Nostalgia products have made a comeback. Whether it’s Timehop’s daily peek down memory lane, Facebook inserting memories1 into your feed, or Apple tucking away old photos into iOS Quick Actions, companies are trying to find meaningful ways to resurface the past.

While I enjoy these features, I’d like to highlight a consequence many of them overlook: causing users to erroneously compare the present to the past. This is especially critical when you’re going through a tough time in life.

I came to this realization after rounding out my third year of journaling every day. Before heading to bed each night, I’ll spend ten minutes recapping the day and picking a single photo to represent it2. This has allowed me to build a personal time capsule like no other. I’d often catch myself randomly recapping old memories while riding the subway home or in-between snoozes before getting out of bed. While reflection is healthy in small, periodic doses, it’s tempting to fall into a trap of comparing where you are now against where you were n years ago. This comparison is helpful in concrete domains such as personal fitness, but breaks down in subjective areas like relationships. For example, I’ve had to overcome the tendency of comparing my happiness while single to how I felt when in past relationships. These two phases are incomparable. Time may be linear, but experiences in my life aren’t. This took me a while to internalize.

A second-order effect of these unfair comparisons is the tendency to view favorable memories as ‘upper bounds’ in life. Culturally, we often do this. Take the adage that your “college years will be the best of your life”, for example. Believing this is shortchanging yourself. Instead, I try to catch myself when I have these thoughts and invert them to lower bounds. Your past should serve as a springboard into the present and future. Another trick I’ve found useful is to check services like Timehop at night, as opposed to the morning. This allows me to tackle the day anew and prevent the aforementioned comparison.

A related (and more general) concept I’ve been thinking about is the notion of write-only feeds. What if we designed a journal that only allowed you to create entries3? This might allow one to experience the cathartic benefits of writing, while reducing the urge to compare distinct points in time. More generally, imagine if services like Twitter allowed tweets to be posted at any time, and only surfaced the feed during specific, limited hours of the day. That could do wonders in eradicating continuous partial attention. But, that’s for another post!

It’s never been easier to revisit old memories—but the repetitive juxtapositions in today’s nostalgia products make it easy to draw inequitable comparisons between who you are now and who you were in the past. The key is applying what you know from the past to make the present and future a beautiful place to be.

1: Also known as “On This Day”

2: I’ll often use screenshots!

3: Only allowing viewing on longer timescales (e.g. 5–10 years)