Observing changes to managed objects across contexts with Combine

A common pattern in Core Data is to fetch objects and show them in your UI using one managed object context, and then use another context to update, insert or delete managed objects. There are several ways for you to update your UI in response to these changes, for example by using an NSFetchedResultsController. I wrote about doing this in a SwiftUI app in an earlier post.

In this week's post I will show you a useful technique that allows you to observe specific managed objects across different contexts so you can easily update your UI when, for example, that managed object was changed on a background queue.

I'll start with a simple, unoptimized version that only listens for updates and move on to expand it into a more flexible and optimized version that can also watch for insertion of objects of a specific type, or notify you when a managed object has been deleted.

We'll use Combine for this and, suprisingly enough, we're not going to build any custom publishers to achieve our goal.

Building a simple managed object observer

The simplest way to observe changes in a Core Data store is by listening for one of the various notifications that are posted when changes occur within Core Data. For example, you can listen for the NSManagedObjectContext.didSaveObjectsNotification notification if you want to know when a managed object context saved objects, and possibly extract them from the notification if needed.

NSManagedObjectContext.didSaveObjectsNotification is available on iOS 14 and above. Prior to iOS 14 this notification was available as Notification.Name.NSManagedObjectContextDidSave.

If you're looking for a more lightweight notification, for example if you only want to determine if a certain object changed, or if you want to materialize your objects in a context other than the one that triggered the notification, you can use NSManagedObjectContext.didMergeChangesObjectIDsNotification to be notified when a specific context merged changes for specific objectIDs into its own context.

Typically you will merge changes that occurred on a background context into your view context automatically by setting the automaticallyMergesChangesFromParent property on your persistent container's viewContext to true. This means that whenever a background context saves managed objects, those changes are merged into the viewContext automatically, providing easy access to updated properties and objects.

Our goal is to make it as easy as possible to be notified of changes to specific managed objects that are shown in the UI. We'll do this by writing a function that returns a publisher that emits values whenever a certain managed object changes.

Here's what the API for this will look like:

class CoreDataStorage {
  // configure and create persistent container
  // viewContext.automaticallyMergesChangesFromParent is set to true

  func publisher<T: NSManagedObject>(for managedObject: T,
                                     in context: NSManagedObjectContext) -> AnyPublisher<T, Never> {

    // implementation goes here
  }
}

The API is pretty simple and elegant. We can pass the managed object that should be observed to this method, and we can tell it which context should be observed. Note that the context that's expected here is the context that we want to observe, not the context that will make the change. In other words, this will usually be your viewContext since that's the context that will merge in changes from background contexts and trigger a UI update.

If you pass the managed object context that makes the changes, you will not receive updates with the implementation I'm about to show you. The reason for that is that the context that makes the changes doesn't merge in its own changes because it already contains them.

If you want to receive updates even if the context that makes changes is also the context that's observed you can use the NSManagedObjectContext.didSaveObjectIDsNotification instead since that will fire for the context that saved (which is the context that made changes) rather than the context that merged in changes.

Note that I made publisher(for:in:) generic so that I can pass any managed object to it and the returned publisher will publish objects of the same type as the managed object that I want to observe.

I will explain more about this setup later, but let's look at the implementation for publisher(for:in:) first:

func publisher<T: NSManagedObject>(for managedObject: T,
                                   in context: NSManagedObjectContext) -> AnyPublisher<T, Never> {

  let notification = NSManagedObjectContext.didMergeChangesObjectIDsNotification
  return NotificationCenter.default.publisher(for: notification, object: context)
    .compactMap({ notification in
      if let updated = notification.userInfo?[NSUpdatedObjectIDsKey] as? Set<NSManagedObjectID>,
         updated.contains(managedObject.objectID),
         let updatedObject = context.object(with: managedObject.objectID) as? T {

        return updatedObject
      } else {
        return nil
      }
    })
    .eraseToAnyPublisher()
}

The code above creates a notification publisher for NSManagedObjectContext.didMergeChangesObjectIDsNotification and passes the context argument as the object that should be associated with the notification. This ensures that we only receive and handle notifications that originated in the target context.

Next, I apply a compactMap to this publisher to grab the notification and check whether it has a list of updated managed object IDs. If it does, I check whether the observed managed object's objectID is in the set, and if it is I pull the managed object into the target context using object(with:). This will retrieve the managed object from the persistent store and associate it with the target context.

Important:
Note that this code does not violate Core Data's threading rules. A managed object's objectID property is the only property that can be safely accessed across threads. It is crucial that the subscriber of the publisher created by this method handles the emitted managed object on the correct context which should be trivial since the context is available from where publisher(for:in:) is called.

If the notification doesn't contain updates, or if the notification doesn't contain the appropropriate objectID I return nil. This will ensure that the the publisher doesn't emit anything if we don't have anything to emit since compactMap will prevent any nil values from being delivered to our subscribers.

Because I want to keep my return type clean I erase the created publisher to AnyPublisher.

To use this simple single managed object observer you might write something like the following code:

class ViewModel: ObservableObject {

  var album: Album // a managed object subclass
  private var cancellables = Set<AnyCancellable>()

  init(album: Album, storage: CoreDataStorage) {
    self.album = album

    guard let ctx = album.managedObjectContext else {
      return
    }

    storage.publisher(for: album, in: ctx)
      .sink(receiveValue: { [weak self] updatedObject in
        self?.album = updatedObject
        self?.objectWillChange.send()
      })
      .store(in: &cancellables)
  }
}

This code is optimized for a SwiftUI app where ViewModel would be used as an @StateObject (or @ObservedObject) property. There are of course several ways for you to publish changes to the outside world, this is just one example. The point here is that you can now obtain a publisher that emits values when your managed object was changed by another managed object context.

While this is a kind of neat setup, it's not ideal. We can only listen for changes to existing managed objects but we can't listen for insert or delete events. We can build a more robust solution that can not only listen for updates, but also deletions or insertions for managed objects of a specific type rather than just a single instance. Let's see how.

Building a more sophisticated observer to publish changes

Before we refactor our implementation, I want to show you the new method signature for publisher(for:in:):

func publisher<T: NSManagedObject>(for managedObject: T,
                                   in context: NSManagedObjectContext,
                                   changeTypes: [ChangeType]) -> AnyPublisher<(T?, ChangeType), Never> {
  // implementation goes here
}

This signature is very similar to what we had before except I've added a ChangeType object. By adding a ChangeType to the publisher(for:in:) function it's now possible to listen for one or more kinds of changes that a managed object might go through. The publisher that we return now emits tuples that contain the managed object (if it's still around) and the change type that triggered the publisher.

This method is useful if you already have a managed object instance that you want to observer.

Before I show you the implementation for this method, here's what the ChangeType enum looks like:

enum ChangeType {
  case inserted, deleted, updated

  var userInfoKey: String {
    switch self {
    case .inserted: return NSInsertedObjectIDsKey
    case .deleted: return NSDeletedObjectIDsKey
    case .updated: return NSUpdatedObjectIDsKey
    }
  }
}

It's a straightforward enum with a computed property to easily access the correct key in a notification's userInfo dictionary later.

Let's look at the implementation for publisher(for:in:changeType:):

func publisher<T: NSManagedObject>(for managedObject: T,
                                   in context: NSManagedObjectContext,
                                   changeTypes: [ChangeType]) -> AnyPublisher<(object: T?, type: ChangeType), Never> {

  let notification = NSManagedObjectContext.didMergeChangesObjectIDsNotification
  return NotificationCenter.default.publisher(for: notification, object: context)
    .compactMap({ notification in
      for type in changeTypes {
        if let object = self.managedObject(with: managedObject.objectID, changeType: type,
                                                         from: notification, in: context) as? T {
          return (object, type)
        }
      }

      return nil
    })
    .eraseToAnyPublisher()
}

I've done some significant refactoring here, but the outline for the implementation is still very much the same.

Since we can now pass an array of change types, I figured it'd be useful to loop over all received change types and check whether there's a managed object with the correct objectID in the notification's userInfo dictionary for the key that matches the change type we're currently evaluating. If no match was found we return nil so no value is emitted by the publisher due to the compactMap that we applied to the notification center publisher.

Most of the work for this method is done in my managedObject(with:changeType:from:in:) method. It's defined as a private method on my CoreDataStorage:

func managedObject(with id: NSManagedObjectID, changeType: ChangeType,
                   from notification: Notification, in context: NSManagedObjectContext) -> NSManagedObject? {
  guard let objects = notification.userInfo?[changeType.userInfoKey] as? Set<NSManagedObjectID>,
        objects.contains(id) else {
    return nil
  }

  return context.object(with: id)
}

The logic here looks very similar to what you've seen in the previous section but it's a bit more reusable this way, and it cleans up my for loop nicely.

To use this new method you could write something like the following:

class ViewModel: ObservableObject {

  var album: Album? // a managed object subclass
  private var cancellables = Set<AnyCancellable>()

  init(album: Album, storage: CoreDataStorage) {
    self.album = album

    guard let ctx = album.managedObjectContext else {
      return
    }

    storage.publisher(for: album, in: ctx, changeTypes: [.updated, .deleted])
      .sink(receiveValue: { [weak self] change in
        if change.type != .deleted {
          self?.album = change.object
        } else {
          self?.album = nil
        }
        self?.objectWillChange.send()
      })
      .store(in: &cancellables)
  }
}

The code above would receive values whenever the observed managed object is updated or deleted.

Let's add one more interesting publisher so we can listen for insertion, updating and deleting of any object that matches a specific managed object subclass. Since the code will be similar to what you've seen before, here's the implementation for the entire method:

func publisher<T: NSManagedObject>(for type: T.Type,
                                   in context: NSManagedObjectContext,
                                   changeTypes: [ChangeType]) -> AnyPublisher<[([T], ChangeType)], Never> {
  let notification = NSManagedObjectContext.didMergeChangesObjectIDsNotification
  return NotificationCenter.default.publisher(for: notification, object: context)
    .compactMap({ notification in
      return changeTypes.compactMap({ type -> ([T], ChangeType)? in
        guard let changes = notification.userInfo?[type.userInfoKey] as? Set<NSManagedObjectID> else {
          return nil
        }

        let objects = changes
          .filter({ objectID in objectID.entity == T.entity() })
          .compactMap({ objectID in context.object(with: objectID) as? T })
        return (objects, type)
      })
    })
    .eraseToAnyPublisher()
}

This method takes a T.Type rather than a managed object instance as its first argument. By accepting T.Type callers can pass the type of object they want to observe. For example by passing Album.self as the type. The AnyPublisher that we create will return an array of ([T], ChangeType) since we can have multiple changes in a single notification and each change can have multiple managed objects.

In the implementation I listen for the same didMergeChangesObjectIDsNotification notification I did before, and then I compactMap over the publisher. Also the same I did before. Inside the closure for the publisher's compactMap I use Array's compactMap to loop over all change types I'm observing. For each change type I check whether there is an entry in the userInfo dictionary, and I extract only the managed object IDs that have an entity description that matches the observed type's entity description. Lastly, I attempt to retrieve objects with all found ids from the target context. I do this in a compactMap because I want to filter out any nil values if casting to T fails.

I then create a tuple of ([T], ChangeType) and return that from the array compactMap. By doing this I create an array of [([T], ChangeType)]. This way of using maps, filters and compact map is somewhat advances, and especially when combined with Combine's compactMap I can see how this might look confusing. It's okay if you have to look at the code a couple of times. Maybe even try to type it line by line if you struggle to make sense of all these maps. Once you get it, it's quite elegant in my opinion.

You can use the above method as follows:

storage.publisher(for: Album.self, in: storage.viewContext, changeTypes: [.inserted, .updated, .deleted])
  .sink(receiveValue: { [weak self] changes in
    self?.storage.viewContext.perform 
      // iterate over changes
      // make sure to do so on the correct queue if applicable with .perform
    }
  })
  .store(in: &cancellables)

I'm very happy with how the call site for this code looks, and the API is certainly a lot cleaner than listening for managed object context notifications all over the place and extracting the information you need. With this setup you have a clean API where all core logic is bundled in a single location. And most importantly, this code does a good job of showing you the power of what Combine can do without the need to create any custom publishers from scratch.

In Summary

In this post you saw how you can use a notification center publisher and a cleverly placed compactMap to build a pretty advanced managed object observer that allows you to see when changes to a specific managed object were merged into a specific context. You started off with a pretty basic setup that we expanded into an API that can be used to observe insertion, deletion and updates for a specific object.

After that, we took it one step further to enable observing a managed object context for changes of one or more types to managed objects of a certain class without needing an instance of that managed object up front by listening to changes to all objects that match the desired type.

The techniques demonstrated in this post all build on fundamentals that you likely have seen before. The interesting bit is that you may have never seen or used these fundamentals in the way that I demonstrated in this post. If you have any suggestions, corrections, questions, or feedback about this post, please send me a message on Twitter.

Understanding the differences between your Core Data model and managed objects

You may have noticed that when Xcode generates your NSManagedObject classes based on your Core Data model file, most of your managed object's properties are optional. Even if you've made them required in the model editor, Xcode will generate a managed object where most properties are optional.

In this article we'll explore this phenomenon, and why it happens.

Exploring generated NSManagedObject subclasses

When you build a project that uses Xcode's automatic code generation for Core Data models, your NSManagedObject subclasses are generated when you build your project. These classes are written your project's Derived Data folder and you shouldn't modify them directly. The models that are generated by Xcode will have optional properties for some of the properties that you've added to your entity, regardless of whether you made the property optional in the model editor.

While this might sounds strange at first, it's actually not that strange. Take a look at the following NSManagedObject subclass:

extension ToDoItem {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<ToDoItem> {
        return NSFetchRequest<ToDoItem>(entityName: "ToDoItem")
    }

    @NSManaged public var completed: Bool
    @NSManaged public var label: String?
}

One of the two properties for my ToDoItem is optional even they're both required in the model editor. When I create an instance of this ToDoItem, I'd use the following code:

let item = ToDoItem(context: managedObjectContext)

A managed object's initializer takes a managed object context. This means that I don't assign a value to the managed properties during the initialization of the ToDoItem. Printing the value for both the label and completed properties yields and interesting result:

print(item.label) // nil
print(item.completed) // false

While label is nil as expected, Core Data assigned a default value of false to the completed property which makes sense because Xcode generated a non-optional property for completed. Let's take it a step further and take a look at the following code:

let item = ToDoItem(context: managedObjectContext)
item.label = "Hello, item"

print(item.label) // "Hello, item"
print(item.completed) // false

do {
  try managedObjectContext.save()
} catch {
  print(error)
}

When you run this code, you'll find that it produces the following output:

Optional("Hello, item")
false
Error Domain=NSCocoaErrorDomain Code=1570 "completed is a required value." UserInfo={NSValidationErrorObject=<CoreDataExample.ToDoItem: 0x60000131c910> (entity: ToDoItem; id: 0x600003079900 <x-coredata:///ToDoItem/t1FABF4F1-0EF4-4CE8-863C-A815AA5C42FF2>; data: {
    completed = nil;
    label = "Hello, item";
}), NSValidationErrorKey=completed, NSLocalizedDescription=completed is a required value.}

This error clearly says completed is a required value. which implies that completed isn't set, and the printed managed object that's shown alongside the error message also shows that completed is nil. So while there is some kind of a default value present for completed, it is not considered non-nil until it's explicitly assigned.

To understand what's happening, we can assign a value to completed and take a look at the printed description for item again:

let item = ToDoItem(context: managedObjectContext)
item.label = "Hello, item"

print(item.completed)
print(item)

This code produces the following output:

false
<CoreDataExample.ToDoItem: 0x6000038749b0> (entity: ToDoItem; id: 0x600001b576e0 <x-coredata:///ToDoItem/tD27C9C9D-A676-4280-9D7C-A1E154B2AD752>; data: {
    completed = 0;
    label = "Hello, item";
})

This is quite interesting, isn't it?

The completed property is defined as a Bool, yet it's printed as a number. We can find the reason for this in the underlying SQLite store. The easiest way to explore your Core Data store's SQLite file is by passing -com.apple.CoreData.SQLDebug 1 as a launch argument to your app and opening the SQLite file that Core Data connects to in an SQLite explorer like SQLite database browser.

Tip:
Learn more about Core Data launch arguments in this post.

When you look at the schema definition for ZTODOITEM you'll find that it uses INTEGER as the type for ZCOMPLETED. This means that the completed property is stored as an integer in the underlying SQLite store. The reason completed is stored as an INTEGER is simple. SQLite does not have a BOOLEAN type and uses an INTEGER value of 0 to represent false, and 1 to represent true instead.

The data that you see printed when you print your managed object instance isn't the value for your completed property, it's the value for completed that will be written to the SQLite store.

There are two things to be learned from this section.

First, you now know that there is a mismatch between the optionality of your defined Core Data model and the generated managed objects. A non-optional String is represented as an optional String in your generated model while a non-optional Bool is represented as a non-optional Bool in your generated model.

Second, you learned that there's a difference between how a value is represented in your managed object model versus how it's represented in the underlying SQLite store. To see which values are used to write your managed object instance to the underlying storage you can print the managed object and read the data field in the printed output.

The main lesson here is that your Core Data model in the model editor and your managed object subclasses do not represent data the same way. Optional in your Core Data model does not always mean optional in your managed object subclass and vice versa. A non-optional value in your Core Data model may be represented as an optional value in your managed object subclass. Core Data will validate your managed object against its managed object model when you attempt to write it to the persistent store and throw errors if it encounters any validation errors.

So why does this mismatch exist? Wouldn't it be much easier if the managed object model and managed object subclasses had a direct mapping?

Understanding the mismatch between managed objects and the Core Data model

A big part of the reason why there's a mismatch between your managed objects and the model you've defined in the model editor comes from Core Data's Objective-C roots.

Since Objective-C doesn't deal with Optional at all there isn't always a good mapping from the model definition to Swift code. Oftentimes, the way the mapping works seems somewhat arbitraty. For example, Optional<String> and Optional<Bool> both can't be represented as a type in Objective-C for the simple reason that Optional doesn't exist in Objective-C. However, Swift and Objective-C can interop with each other and Optional<String> can be bridged to an NSString automatically. Unfortunately Optional<Bool> can't be mapped to anything in Objective-C automatically as Xcode will tell you when you attempt to define an @NSManaged property as Bool?.

If you've never worked with Objective-C it might seem very strange to you that there is no concept of Optional. How did folks use optional properties in Core Data before Swift? And what happens when something is supposed to be nil in Objective-C?

In Objective-C it's perfectly fine for any value to be nil, even when you don't expect it. And since Core Data has its roots in Objective-C some of this legacy carries over to your generated Swift classes in a sometimes less than ideal manner.

The most important takeaway here isn't how Objective-C works, or how Xcode generates code exactly. Instead, I want you to remember that the types and configuration in your Core Data model definition do not (have to) match the types in your (generated) managed object subclass.

In Summary

In this week's article you've learned a lot about how your managed object subclasses and Core Data model definition don't always line up the way you'd expect them to. You saw that sometimes a non-optional property in the model editor can end up as optional in the generated managed object subclass, and other times it ends up as a non-optional property with a default value even if you didn't assign a default value yourself.

You also saw that if a default value is present on a managed object instance it doesn't mean that the value is actually present at the time you save your managed object unless you explicitly defined a default value in the Core Data model editor.

While this is certainly confusing and unfortunate, Core Data is pretty good at telling you what's wrong in the errors it throws while saving a managed object. It's also possible to inspect the values that Core Data will attempt to store by printing your managed object instance and inspecting its data attribute.

On a personal note I hope that the behavior I described in this week's article is addressed in a future update to Core Data that makes it more Swift friendly where the managed object subclasses have a closer, possibly direct mapping to the Core Data model that's defined in a model editor. But until then, it's important to understand that the model editor and your managed object subclasses do not represent your model in the same way, and that this is at least partially related to Core Data's Objective-C roots.

If you have any questions, corrections or feedback about this post please let me know on Twitter. This post is part of some of the research, exploration and preparation that I'm doing for a book about Core Data that I'm working on. For updates about this book make sure to follow me on Twitter. I'm currently planning to release the book around the end of 2020.

Understanding how DispatchQueue.sync can cause deadlocks

As a developer, you'll come across the term "deadlock" sooner or later. When you do, it's usually pretty clear that a deadlock is bad (the name alone implies this) and if you've experienced one in your code, you'll know that your application crashes with EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) when a deadlock occurs.

A few weeks ago, I wrote about dispatching code synchronously and asyncronously. Several people pointed out that that post does not mention deadlocks. Instead of making that post longer and more complicated, I decided to make this week's post all about deadlocks and understanding what they are.

If you're not familiar with DispatchQueue.sync and DispatchQueue.async I would recommend that you read the article I linked in the previous paragraph before continuing. It'll make following this week's article much easier.

What happens when a deadlock occurs?

In short, a deadlock can occur when the system is waiting for a resource to free up while it's logically impossible for that resource to become available. You can think of this resource as almost anything. It could be a database handle, a file on the filesystem, or even time to run code on the CPU.

So more broadly speaking you're code is waiting for something to happen before it runs, but that something will never happen.

In my previous article I explained dispatching code on a dispatch queue using a restaurant analogy. Let's continue this analogy to explain deadlocks more clearly.

Remember how I explained that if a waiter dispatches orders to the kitchen synchronously the waiter would have to wait and do nothing while the chef prepares a meal and hands it to the waiter? This means that any tasks that are delegated to the waiter must occur after the chef has prepped the meal.

If one of the restaurant's customers wants to order a drink the waiter will not take this order until the chef has prepared the meal that the waiter asked for.

I know, this restaurant sounds terrible.

Now imagine that our waiter dispatched an order for a soup synchronously but they forgot to note down what kind of soup the customer wanted. The note just says "1 soup". So the chef asks the waiter to go back to the customer and ask them for the type of soup they wanted. The chef dispatches this synchronously to the waiter. This means that the chef won't continue to work on any other meals until the waiter returns and tells the chef what soup should be served.

And the waiter replies: "Sure, I'll ask after you serve the soup".

The restaurant is now in a deadlock. No more meals will be prepared, no orders will be taken, no meals will be served.

Why? You ask.

The waiter dispatched an order to the chef synchronously. So the waiter will not do anything until the order is fulfilled.

At the same time, the chef needs information from the waiter to continue so the chef dispatches a question to the waiter synchronously.

And since the chef did not yet fulfill the order, the waiter can not go and ask the customer for the type of soup they wanted. Without the information, the chef cannot complete the soup.

Luckily, restaurants are asynchronous in how they are usually set up so a situation like this should never occur. However, if you're dispatching synchronously in your code regularly, odds are that your program might end up in a deadlock.

Understanding a deadlock in Swift

One way to deadlock a program in Swift is to dispatch synchronously to the current thread. For example:

let queue = DispatchQueue(label: "my-queue")
queue.sync {
  print("print this")

  queue.sync {
    print("deadlocked")
  }
}

Putting this code anywhere in your app will immediately result in a crash before the second print statement runs. The queue is running code synchronously. The second closure can't run until the first one completes. The first closure can't complete until the second closure is run since its dispatched synchronously.

Or as the official documentation for DispatchQueue.sync states:

Calling this function and targeting the current queue results in deadlock.

This is exactly what the code above does, and the situation is almost identical to the one described in the restaurant analogy earlier.

However, in the analogy, we were dealing with two "queues" (the waiter and the chef). Can we write code that models this? Of course we can!

let waiter = DispatchQueue(label: "waiter")
let chef = DispatchQueue(label: "chef")

// synchronously order the soup
waiter.sync {
  print("Waiter: hi chef, please make me 1 soup.")

  // synchronously prepare the soup
  chef.sync {
    print("Chef: sure thing! Please ask the customer what soup they wanted.")

    // synchronously ask for clarification
    waiter.sync {
      print("Waiter: Sure thing!")

      print("Waiter: Hello customer. What soup did you want again?")
    }
  }
}

The code here is somewhat more complicated than what you saw before but the effect is the same. The waiter will never ask for clarification because the chef's sync work never finishes. The waiter and chef queues are waiting for each other to finish which means that neither of them will finish.

In this case, both queues and all sync calls are close to each other so debugging isn't terrible. However, in practice, you'll find that it's much harder to unravel and untangle your code and figure out how a deadlock occurs. Especially if you use sync a lot (which you generally shouldn't due to its blocking nature).

Resolving the deadlock, in this case, is simple. The waiter could dispatch the order synchronously. Or the chef could prepare the meal asynchronously. Or the waiter could even ask for clarification asynchronously. Making any of the three steps async would fix this deadlock.

The real question is whether any of these three tasks should really be sync. In a real restaurant, all of these tasks would be dispatched async and the restaurant would never deadlock. It would also run much faster and smoother than a restaurant where most things are done synchronously.

Avoiding deadlocks by using sync responsibly

While making things async is an easy fix for virtually any dispatch queue related deadlock, it's not always desirable. One thing to look out for when you're dispatching sync is that you do this from a controlled location, and avoid running any work submitted by an external party. So for example, you wouldn't want to do the following:

func run(_ closure: @escaping () -> Void) {
  myQueue.sync {
    closure()
  }
}

You don't know what the code inside the closure does so it might very well contain another call to run which would mean that your run function is now causing a hard to track down deadlock.

A better example of using sync is the date formatter cache I showed in the previous article about dispatch queues:

class DateFormatterCache {
  private var formatters = [String: DateFormatter]()
  private let queue = DispatchQueue(label: "DateFormatterCache: \(UUID().uuidString)")

  func formatter(using format: String) -> DateFormatter {
    return queue.sync { [unowned self] in
      if let formatter = self.formatters[format] {
        return formatter
      }

      let formatter = DateFormatter()
      formatter.locale = Locale(identifier: "en_US_POSIX")
      formatter.dateFormat = format
      self.formatters[format] = formatter

      return formatter
    }
  }
}

This formatter object can be used to request (and cache) data formatters in a thread-safe manner. By dispatching access to the formatters dictionary synchronously we can make sure that the formatters dictionary accessed and updated atomically. This means that modifying or accessing the formatters dictionary is isolated from other operations that might also require access to formatters.

Since the queue that the work is dispatched onto is private it's not possible for other actors to dispatch synchronously to this queue. We also know that there is no recursive access possible withing the formatter(using:) function so sync is used appropriately here.

In Summary

In this week's post, you learned what a deadlock is by building upon the restaurant analogy I used earlier to explain dispatching code synchronously or asynchronously. You saw how dispatching synchronously leads to deadlocks when tasks start waiting for each other.

You also learned how you can easily resolve deadlocks (once you've tracked them down) by not dispatching code synchronously. In general you should be very cautious when using sync since it's easy to accidentally cause a deadlock with it. However, sometimes you might need atomic access for a dictionary, array or another resource. In these cases sync can be a useful tool to make operations atomic.

If you have any questions about this post, or if you have feedback for me please reach out on Twitter.

Configuring error types when using flatMap in Combine

When you're using Combine for an app that has iOS 13 as its minimum deployment target, you have likely run into problems when you tried to apply a flatMap to certain publishers in Xcode 12. To be specific, you have probably seen the following error message: flatMap(maxPublishers:_:) is only available in iOS 14.0 or newer. When I first encountered this error I was somewhat stumped. Surely we had flatMap in iOS 13 too!

If you've encountered this error and thought the same, let me reassure you. You're right. We had flatMap in iOS 13 too. We just gained new flavors of flatMap in iOS 14, and that's why you're seeing this error.

Before I explain more about the new flatMap flavors that we gained in iOS 14, let's figure out the underlying error that causes flatMap(maxPublishers:_:) is only available in iOS 14.0 or newer in your project.

Understanding how flatMap works with Error types

In Combine, every publisher has a defined Output and Failure type. For example, a DataTaskPublisher has a tuple of Data and URLResponse as its Output ((data: Data, response: URLResponse)) and URLError as its Failure.

When you want to apply a flatMap to the output of a data task publisher like I do in the following code snippet, you'll run into a compiler error:

URLSession.shared.dataTaskPublisher(for: someURL)
  .flatMap({ output -> AnyPublisher<Data, Error> in

  })

If you've written code like this before, you'll probably consider the following compiler error to be normal:

Instance method flatMap(maxPublishers:_:) requires the types URLSession.DataTaskPublisher.Failure (aka URLError) and Error be equivalent

I've already mentioned that a publisher has a single predefined error type. A data task uses URLError. When you apply a flatMap to a publisher, you can create a new publisher that has a different error type.

Since your flatMap will only receive the output from an upstream publisher but not its Error. Combine doesn't like it when your flatMap returns a publisher with an error type that does not align with the upstream publisher. After all, errors from the upstream are sent directly to subscribers. And so are errors from the publisher created in your flatMap. If these two errors aren't the same, then what kind of error does a subscriber receive?

To avoid this problem, we need to make sure that an upstream publisher and a publisher created in a flatMap have the same Failure. One way to do this is to apply mapError to the upstream publisher to cast its errors to Error:

URLSession.shared.dataTaskPublisher(for: someURL)
  .mapError({ $0 as Error })
  .flatMap({ output -> AnyPublisher<Data, Error> in

  })

Since the publisher created in the flatMap also has Error as its Failure, this code would compile just fine as long as we would return something from the flatMap in the code above.

You can apply mapError to any publisher that can fail, and you can always cast the error to Error since in Combine, a publisher's Failure must conform to Error.

This means that if you're returning a publisher in your flatMap that has a specific error, you can also apply mapError to the publisher created in your flatMap to make your errors line up:

URLSession.shared.dataTaskPublisher(for: someURL)
  .mapError({ $0 as Error })
  .flatMap({ [weak self] output -> AnyPublisher<Data, Error> in
    // imagine that processOutput is a function that returns AnyPublisher<Data, ProcessingError>
    return self?.processOutput(output) 
      .mapError({ $0 as Error })
      .eraseToAnyPublisher()
  })

Having to line up errors manually for your calls to flatMap can be quite tedious but there's no way around it. Combine can not, and should not infer anything about how your errors should be handled and mapped. Instead, Combine wants you to think about how errors should be handled yourself. Doing this will ultimately lead to more robust code since the way errors propagate through your pipeline is very explicit.

So what's the deal with "flatMap(maxPublishers:_:) is only available in iOS 14.0 or newer" in projects that have iOS 13.0 as their minimum target? What changed?

Fixing "flatMap(maxPublishers:_:) is only available in iOS 14.0 or newer"

Everything you've read up until now applies to Combine on iOS 13 and iOS 14 equally. However, flatMap is slightly more convenient for iOS 14 than it is on iOS 13. Imagine the following code:

let strings = ["https://donnywals.com", "https://practicalcombine.com"]
strings.publisher
  .map({ url in URL(string: url)! })

The publisher that I created in this code is a publisher that has URL as its Output, and Never as its Failure. Now let's add a flatMap:

let strings = ["https://donnywals.com", "https://practicalcombine.com"]
strings.publisher
  .map({ url in URL(string: url)! })
  .flatMap({ url in
    return URLSession.shared.dataTaskPublisher(for: url)
  })

If you're using Xcode 12, this code will result in the flatMap(maxPublishers:_:) is only available in iOS 14.0 or newer compiler error. While this might seem strange, the underlying reason is the following. When you look at the upstream failure type of Never, and the data task failure which is URLError you'll notice that they don't match up. This is a problem since we had a publisher that never fails, and we're turning it into a publisher that emits URLError.

In iOS 14, Combine will automatically take the upstream publisher and turn it into a publisher that can fail with, in this case, a URLError.

This is fine because there's no confusion about what the error should be. We used to have no error at all, and now we have a URLError.

On iOS 13, Combine did not infer this. We had to explicitly tell Combine that we want the upstream publisher to have URLError (or a different error) as its failure type by calling setFailureType(to:) on it as follows:

let strings = ["https://donnywals.com", "https://practicalcombine.com"]
strings.publisher
  .map({ url in URL(string: url)! })
  .setFailureType(to: URLError.self) // this is required for iOS 13
  .flatMap({ url in
    return URLSession.shared.dataTaskPublisher(for: url)
  })

This explains why the compiler error said that flatMap(maxPublishers:_:) is only available on iOS 14.0 or newer.

It doesn't mean that flatMap isn't available on iOS 13.0, it just means that the version of flatMap that can be used on publishers the have Never as their output and automatically assigns the correct failure type if the flatMap returns a publisher that has something other than Never as its failure type is only available on iOS 14.0 or newer.

If you don't fix the error and you use cmd+click on flatMap and then jump to definition you'd find the following definition:

@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
extension Publisher where Self.Failure == Never {

  /// Transforms all elements from an upstream publisher into a new publisher up to a maximum number of publishers you specify.
  ///
  /// - Parameters:
  /// - maxPublishers: Specifies the maximum number of concurrent publisher subscriptions, or ``Combine/Subscribers/Demand/unlimited`` if unspecified.
  /// - transform: A closure that takes an element as a parameter and returns a publisher that produces elements of that type.
  /// - Returns: A publisher that transforms elements from an upstream publisher into a publisher of that element’s type.
  public func flatMap<P>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -> P) -> Publishers.FlatMap<P, Publishers.SetFailureType<Self, P.Failure>> where P : Publisher
}

This extension is where the new flavor of flatMap is defined. Notice that it's only available on publishers that have Never as their Failure type. This aligns with what you saw earlier.

Another flavor of flatMap that was added in iOS 14 is similar but works the other way around. When you have a publisher that can fail and you create a publisher that has Never as its Failure in your flatMap, iOS 14 will now automatically keep the Failure for the resulting publisher equal to the upstream's failure.

Let's look at an example:

URLSession.shared.dataTaskPublisher(for: someURL)
  .flatMap({ _ in
    return Just(10)
  })

While this example is pretty useless on its own, it demonstrates the point of the second flatMap flavor really well.

The publisher created in this code snippet has URLError as its Failure and Int as its Output. Since the publisher created in the flatMap can't fail, Combine knows that the only error that might need to be sent to a subscriber is URLError. It also knows that any output from the upstream is transformed into a publisher that emits Int, and that publisher never fails.

If you have a construct similar to the above and want this to work with iOS 13.0, you need to use setFailureType(to:) inside the flatMap:

URLSession.shared.dataTaskPublisher(for: someURL)
  .flatMap({ _ -> AnyPublisher<Int, URLError> in
    return Just(10)
      .setFailureType(to: URLError.self) // this is required for iOS 13
      .eraseToAnyPublisher()
  })

The code above ensures that the publisher created in the flatMap has the same error as the upstream error.

In Summary

In this week's post, you learned how you can resolve the very confusing flatMap(maxPublishers:_:) is only available in iOS 14.0 or newer error in your iOS 13.0+ projects in Xcode 12.0.

You saw that Combine's flatMap requires the Failure types of the upstream publisher that you're flatMapping over and the new publisher's to match. You learned that you can use setFailureType(to:) and mapError(_:) to ensure that your failure types match up.

I also showed you that iOS 14.0 introduces new flavors of flatMap that are used when either the upstream publisher or the publisher created in the flatMap has Never as its Failure because Combine can infer what errors a subscriber should receive in these scenarios.

Unfortunately, these overloads aren't available on iOS 13.0 which means that you'll need to manually align error types if your project is iOS 13.0+ using setFailureType(to:) and mapError(_:). Even if your upstream or flatMapped publishers have Never as their failure type.

If you have any questions or feedback for me about this post, feel free to reach out on Twitter.

3 tips to work through a technical coding interview

If you're a programmer looking for a job it's very likely that you'll have to do a coding interview at some point. Every company conducts these interviews differently. Some may have you work through several heavy computer science problems, others may present you with a task that's related to the job you're interviewing for and others might do both.

No matter what the exact form is, you'll want to nail these interviews as they are a big part of whether you'll get an offer or not.

In my career, I haven't had to go through extensive coding interviews myself. That's in part because I've interviewed with smaller, local companies most of the time and in part because until recently coding challenges just weren't that common where I'm from.

A while ago I was talking to a coworker about coding interviews and not only has he gone through more coding interviews than I have, he's also been on the interviewer's side far more often than I have been.

Based on that conversation, and the brief mock coding challenge I completed to see what our interview process was like (the company I was hired at got bought by my current employer so I never went through their interview process), I would like to share three things with you that I have learned from talking to many people about their interviewing experiences over the past few years.

This list obviously isn't a complete list made up of everything you could possibly do to nail a coding interview, and it might not even apply to every interview out there. After all, every country, company, and interview is different and there's no silver bullet to doing well on every coding interview.

1. Study

The tip I'd like to open this top three with is very important, but also my least favorite tip. If you want to do well during a coding interview you'll have to sit down and study. Many coding interviews will contain problems that are common computer science, or general programming problems and you'll have to understand how to best solve these problems.

However, just learning the solution to each problem might not be enough. You'll want to learn why a certain solution is a good fit for a certain problem.

If you can produce a perfect solution to an interviewer's question but you can't explain how that solution works, why it's a good solution, or how it performs, the interviewer will see that you studied, which might score you some point, but they can't determine whether or not you understand what you're doing.

A resource that I've often seen recommended and sometimes peek into myself is Cracking the Coding Interview. This book contains a ton of interview questions, theory, and answers based on the interview processes at companies like Facebook, Google, Microsoft, and more. This book is a very popular and common resource which means that it's not unlikely for you to encounter questions from this book in an interview.

If you're interviewing for an iOS job, I recommend you take a look at Paul Hudson's iOS interview questions page which contains a ton of questions that are used in iOS interviews regularly.

2. Think out loud

You might not know the answer to every question, and that's fine most of the time. Especially if the interviewer asks you to write the implementation for a specific algorithm on a whiteboard (or a Swift Playground) on the spot.

These kinds of coding on the spot questions can be jarring, especially if you're not confident whether your answer is the correct answer.

Usually, the interviewer isn't only interested in the correct answer. They are interested in seeing how you solve problems, and how you think about them. A good way to demonstrate your thought process is to think out loud.

Thinking out loud can be very scary at first. You might say something silly, or describe something poorly even though you didn't mean to. That's all perfectly fine. I wouldn't recommend rambling every thought you have, but any thoughts about the problem and your solution will help the interviewer see how you think, and how you work through a problem.

Not only will the interviewer be able to pick up on your though process, you might actually help yourself solve the problem much better than you would if you remained quiet. It's almost like you're rubber ducking your way through the problem. Simply talking through it out loud can help you spot flaws in your reasoning and guide you to the correct solution.

3. Ask questions

My third tip flows nicely from the second tip. While you're talking out loud, you might find that you have questions about the problem you're trying to solve. If this happens, you should ask these questions to the interviewer. Sometimes you might discover an edge case and be unsure of whether or not that edge case should be covered in your implementation.

Other times a requirement might not be quite clear and it helps to ask questions and clarify your doubts.

By asking questions you show the interviewer that you pay attention to details, and care about confirming whether or not you understood the task. And a couple of well-asked questions might even show the interviewer that you understand why a specific problem is non-trivial.

It could even show that you understand that a specific question has two valid solutions that each have different pros and cons and that you want to make sure you understand which solution fits best.

(bonus) 4. Be honest

If you really get stuck on a problem, and have no idea where to start don't bluf your way through the problem. The interviewer will be able to tell, and while you're most likely bluffing because you really want the job, the interview might consider that to be reckless.

They will immediately wonder whether or not you might bluf your way through your day to day work too.

It's much better to admit that you're not sure about a solution, and then work your way through the problem. Think out loud, ask questions where needed and apply the principles that you've studies while preparing.

I'm sure you'll be amazed how far you're able to come, and how much knowledge you'll get to show off even if you don't fully understand the problem you're asked to solve during the interview.

In Summary

In this week's post you saw a couple of tips that will hopefully help you get through (and nail) your next coding interview.

Keep in mind that every person is different, and that every interview is different too. Not every company will require a very technical coding interview, and others will have a full day of technical interviews. There's no silver bullet, and no one size fits all solution to do well in any coding interview.

That said, I really hope that these tips will help you prepare for your next interview, and that you'll get the offer you're hoping to get. If you have any questions, additions, or feedback for this list then please let me know on Twitter.

Dispatching async or sync? The differences explained

When writing iOS apps, we regularly run into code that is asynchronous. Sometimes you know you're writing something that will run asynchronously and other times you're passing a completion handler to code that may or may not run asynchronously on a different dispatch queue.

If you're familiar with using DispatchQueue.main, you have probably written code like this:

DispatchQueue.main.async {
  // do something
}

And while writing this, you may have encountered a second method on DispatchQueue.main called sync.

In this week's post I will explain the difference between sync and async, and you will learn when you might want to use these different methods.

Note that this article assumes a little bit of knowledge about DispatchQueue and asynchronous programming as I won't be covering the basics of these topics in this post.

Understanding DispatchQueue.async

Every DispatchQueue instance has an async method. Regardless of whether you're using DispatchQueue.main.async, DispatchQueue.global().async, or if you create a custom queue.

Every dispatch queue has an async method that schedules a chunk of work that's in the closure it receives to be executed at a later time (asynchronously).

When you use a dispatch queue's async method you ask it to perform the work in your closure, but you also tell it that you don't need that work to be performed right now, or rather, you don't want to wait for the work to be done.

Imagine working in a restaurant as a waiter. Your job is to accept orders from guests and relay them to the kitchen. Every time you take a guest's order, you walk to the kitchen and pass them a note with the dishes that they need to prepare and you move on to take the next order. Eventually the kitchen notifies you that they have completed an order and you can pick up the order and take it to the guest's table.

In this analogy, the waiter can be considered its own dispatch queue. You might consider it the main dispatch queue because if the waiter is blocked, no more orders are taken and the restaurant grinds to a halt (assuming that it's a small restaurant with only a single waiter). The kitchen can be thought of as a different dispatch queue that the waiter calls async on with an order every time it asks for an order to be made.

As a waiter you dispatch the work and move on to do the next task. Because you dispatch to the kitchen asynchronously nobody is blocked and everybody can perform their jobs.

The above analogy explains dispatching asyncronously from one queue to another, but it doesn't explain dispatching asyncronously from within the same queue.

For example, there's nothing stopping you from calling DispatchQueue.main.async when you're already on the main queue. So how does that work?

It's quite similar, really. When you dispatch asynchronously within the same queue, then the body of work that should be executed is executed right after whatever it is the queue is currently doing.

Coming back to the waiter analogy, if you walk past a table and tell them "I'll be right with you to take your order" and you're currently delivering drinks to another table, you would essentially be dispatching asyncronously to yourself. You've scheduled a body of work to be done but you also don't want to block what you're doing now.

To summarize, DispatchQueue.async allows you to schedule work to be done using a closure without blocking anything that's ongoing. In most cases where you need to dispatch to a dispatch queue you'll want to use async. However, there are most certainly cases where DispatchQueue.sync makes sense as you'll see in the next section.

Understanding DispatchQueue.sync

Dispatching work synchronously can be a slippery slope. It can be quite tempting to simplify an asyncronous API and replace it with sync. Especially if you're not too familiar with DispatchQueue.sync, you might think that even though you're dispatching synchronously you're not blocking the queue you're on since the work runs on a different queue.

Unfortunately, this isn't entirely true. Let's go back to the restaurant from the previous section.

I explained how a waiter dispatches meal preparation to the kitchen asyncronously. This allows the waiter to continue taking orders and bringing meals to guests.

Now imagine that the waiter would ask the kitchen for an order and then stood there waiting. Doing nothing. The waiter isn't blocked by work they're doing themselves but instead, they're blocked because they have to wait for the chef to prepare the dish they asked for.

This is exactly what DispatchQueue.sync does. When you dispatch a body of work using sync, the current queue will wait for the body of work to complete until it can continue doing any work.

I wrote about DispatchQueue.sync briefly in my post on an @Atomic property wrapper where I explained why that property wrapper doesn't quite work for types like Array and Dictionary.

By far the most common case where I've used DispatchQueue.sync is for a purpose similar to the @Atomic property wrapper, which is to ensure that certain properties or values can only be modified synchronously to avoid multithreading problems.

A very simple example would be this DateFormatterCache object:

class DateFormatterCache {
  private var formatters = [String: DateFormatter]()
  private let queue = DispatchQueue(label: "DateFormatterCache: \(UUID().uuidString)")

  func formatter(using format: String) -> DateFormatter {
    return queue.sync { [unowned self] in
      if let formatter = self.formatters[format] {
        return formatter
      }

      let formatter = DateFormatter()
      formatter.locale = Locale(identifier: "en_US_POSIX")
      formatter.dateFormat = format
      self.formatters[format] = formatter

      return formatter
    }
  }
}

Every time the formatter(using:) function is called on an instance of DateFormatterCache, that work is dispatched to a specific queue synchronously. Dispatch queue are serial by default which means that they perform every task that they're asked to perform one by one, in the same order that the tasks were scheduled in.

This means that we know for sure that there's only one task at a time that accesses the formatters dictionary, reads from it, and caches a new formatter if needed.

If we would call formatter(using:) from multiple threads at the same time we really need this synchronization behavior. If we wouldn't have that, multiple threads would read from the formatters dictionary and write to it which means that we'd end up creating the same date formatter multiple times and we might even run into scenarios where formatters go missing from the cache entirely.

If you prefer to think of this in our restaurant analogy, think of it as all guests in a restaurant being able to write their order on a piece of paper. The waiter is only allowed to pass one piece of paper to the kitchen and that piece of paper must contain all orders. Every time a guest asks the waiter for the ordering paper the waiter will give a copy of its currently known list of orders to the guest.

So if two guests come in at the same time, they both get an empty piece of paper. And when they come back to return the paper to the waiter, the waiter can only keep the last one they receive. The next guests do the same and ultimately the waiter will have a very messy and incomplete order for the kitchen.

This is obviously not what you want so the waiter should process requests synchronously to make sure that every order is written down, and no orders go missing.

In Summary

In this week's post you learned about two very interesting DispatchQueue methods: sync and async. You learned what each of these two methods do, and how they are used in practice.

Keep in mind that async is often the method you want when dispatching work to a queue, and that sync can be very important when you want to have atomic operations and ensure a level of thread safety for dictionaries, arrays, and more.

If you have questions about this post, or if you have feedback for me, I'd love to hear from you on Twitter.

Implementing a one-way sync strategy with Core Data, URLSession and Combine

A common use of a Core Data store is to cache data from a remote resource locally to support offline functionality of an app. There are multiple ways to implement a caching mechanism and many of them don't involve Core Data. For example, you could simply fetch a JSON file from a server and store the contents of that JSON file on disk.

A downside of fetching a full data set every time is that you risk using a lot of bandwidth, especially if your data set is large, or if your data set is expected to grow over time.

An alternative might be to send a parameter to your server to tell the server when you last fetched data, which means that the server can respond with updated records only, and you can merge these updates into your local store. Using Core Data in this case helps, but you can achieve this with plain SQLite too.

Regardless of being able to achieve this functionality with other tools than Core Data, I want to talk about implementing a sync strategy on top of Core Data simply because I like the technology and have been using it for years now with little to no problems.

By the end of this post you should have a solid idea of what a basic sync strategy looks like. We'll only focus on fetching data and caching it. If your application modifies data and sends this modified data back to the server your strategy will need some modifications that I won't cover in this article.

In other words, this article explains how you can implement a one-way sync strategy.

Defining the server-side expectations

Syncing data is usually a non-trivial task, especially if you don't want to fetch all data from the server every time. Throughout this article I'm going to assume that you're working with a server that is capable of sending you only the changes from a given point in time. However, if your server returns a full data set instead of only changes, the sync strategy in this post should work the same. The only difference would be that a full data set uses more bandwidth and might take more time to process depending on the size of the data set.

The exact shape of the objects that a server returns doesn't matter much, the most important point is that objects that your server returns have a unique identifier. Consider the following JSON response:

{
  "events": [
    {
      "id": 1,
      "title": "My Event",
      "location": {
        "id": 2,
        "name": "Donny's House"
      }
    }
  ]
}

This response contains an array of events. Each event has a unique id, and it has a property called location. This location property will be imported as a relationship between an Event object and a Location object.

We can use this structure just fine for new objects and for modified objects. A potential downside of working with this structure is that a modified location would trigger changes on one or more events which means that we do more processing than needed. An easy workaround would be to have the server include a seperate key for modified locations:

{
  "events": [
    {
      "id": 1,
      "title": "My Event",
      "location": {
        "id": 2,
        "name": "Donny's House"
      }
    }
  ],
  "locations": [
    {
      "id": 3,
      "name": "Melkweg, Amsterdam"
    }
  ]
}

Having this seperate locations key is useful if a location changed but the event that it belongs to remains the same. If both the location and the event changed the server should probably omit the location from the locations array in the JSON since it'll also be part of an event in the events array. I consider this to be a server-side optimization that can be made which is outside of the scope of this post.

At this point the JSON can tell us about objects that are new or modified but not about objects that are deleted. Since we don't need a full object to know what to delete we can include a deleted key in the json that contains any deleted event or location ids:

{
  "events": [
    {
      "id": 1,
      "title": "My Event",
      "location": {
        "id": 2,
        "name": "Donny's House"
      }
    }
  ],
  "locations": [
    {
      "id": 3,
      "name": "Melkweg, Amsterdam"
    }
  ],
  "deleted": {
    "events": [2, 8],
    "locations": [4, 7]
  }
}

And lastly, the server should provide us with a key, or timestamp, that we can use for the next request. The reason we want the server to provide a timestamp rather than letting the client determine one is that we shouldn't rely on the client's clock to make decisions on the server. Furthermore, if the server is in charge of generating these keys it can decide for itself whether this key is a timestamp or something else.

This means that the full JSON I'm working from in this post looks like this:

{
  "events": [
    {
      "id": 1,
      "title": "My Event",
      "location": {
        "id": 2,
        "name": "Donny's House"
      }
    }
  ],
  "locations": [
    {
      "id": 3,
      "name": "Melkweg, Amsterdam"
    }
  ],
  "deleted": {
    "events": [2, 8],
    "locations": [4, 7]
  },
  "version_token": "1234567890"
}

Your server's responses might look completely different, and that's perfectly fine. My goal is to explain the strategy I'm building on top of this response in a way that will allow you to write your own sync strategy even if your server is completely different.

Configuring your Core Data store

When you implement a sync strategy that writes remote data to a local Core Data store it's crucial that you prevent data duplication. While Core Data should typically not be treated as a store that has a concept of primary keys, we can apply a unique constraint on one or more properties of a Core Data model.

To do this, open the Core Data model editor, select the Entity that needs a unique contraint and use the Data model inspector in the right-hand sidebar to add Constraints. You can provide a list of comma-separated values if you want to determine uniqueness on a combination of multiple properties.

When you add a unique constraint like this, Core Data will consider it an error if you try to save an object with a duplicate value for the key you provided. So in this case, saving two Event objects with the same id would result in an error.

Before I explain why this error is useful and how you can use it to your advantage, I want to work a little bit on our data importer. In an earlier post I explained how you can build a Core Data abstraction that doesn't rely on the AppDelegate or any of Apple's default templates for SwiftUI or UIKit applications.

I'm going to assume that you have a similar setup in your application but ultimately it won't matter much how you've set up your application. The following code can be used as a simple starting point for your data importer:

class DataImporter {
  let importContext: NSManagedObjectContext

  init(persistentContainer: NSPersistentContainer) {
    importContext = persistentContainer.newBackgroundContext()
    importContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
  }
}

You can create an instance of this DataImporter wherever you need it in your application. Typically this will be somewhere in a ViewModel or other place that you would normally use to make network requests, fetch data, or perform other potentially slow and costly operations.

The most important part of the snippet above is this:

importContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

By setting the mergePolicy for the background context to NSMergeByPropertyObjectTrumpMergePolicy we tell Core Data to resolve any conflicts between the store and the data we're attempting to save using the properties from the object we want to save. This means that if we have a stored Event, and want to save an event with the same id as the Event that's already stored, Core Data will overwrite the stored event's values with the new event's values.

This is perfect for an application where we want to sync data from the server into our local data store. It's save to assume that the server has the most recent and correct version of every object, so whenever we import we'll want to overwrite any existing data with data from the server.

Since we'll be importing data on a background context and the persistent container's viewContext should pick up any changes we make automatically, we'll need to set the viewContext's automaticallyMergesChangesFromParent property to true. If you're using one of Apple's premade templates you can insert container.viewContext.automaticallyMergesChangesFromParent = true at the point where the container is created.

If you're using a custom abstraction you can do the same except you'll be adding this line in code you wrote yourself.

Setting automaticallyMergesChangesFromParent = true will make sure that the viewContext is aware of any changes that were made to the persistent container. When you save a background context, the persistent container is automatically informed of the changes that were made. The viewContext is considered to be a child of the persistent container so when you set automaticallyMergesChangesFromParent to true, the viewContext will automatically be made aware of changes in the persistent container.

This is particularly useful if your UI uses an NSFetchedResultsController. When your viewContext does not automatically merge changes from its parent, your NSFetchedResultsController won't automatically update when your background context saves after running your import.

One last thing you should do before writing your importer logic is to make sure you have a way to transform your JSON into managed objects. One way is to make your managed objects work with Codable. Since I have a full post that already covers this I won't explain how to do this in this post.

Writing your import logic

Now that you have a Core Data store set up and you know what the server's response data looks like, let's implement our importer logic.

Since the server returns a version_token key that should be used to ask the server for changes after we've done an initial import, the importer should keep track of this token.

I'll store it in UserDefaults since it's just a single value and we don't need to keep any history of it:

class DataImporter {
  var versionToken: String? {
    get { UserDefaults.standard.string(forKey: "DataImporter.versionToken") }
    set { UserDefaults.standard.set(newValue, forKey: "DataImporter.versionToken") }
  }

  // existing initializer
}

We'll also need to define a Decodable struct that's used to decode the server's response into:

struct ImporterResponse: Decodable {
  let events: [Event]
  let locations: [Location]
  let deleted: ImporterResponse.Deleted
  let versionToken: String
}

extension ImporterResponse {
  struct Deleted: Decodable {
    let events: [Int]
    let locations: [Int]
  }
}

I'm using a nested struct to decode the deleted items into. I will set my JSONDecoder's keyDecodingStrategy to convertFromSnakeCase so the version_token from the JSON is converted to versionToken in my struct.

Now that we have something to decode the response into we can write the import function:

func runImport() {
  // 1. Build the correct URL
  var url = URL(string: "https://www.mywebsite.com/datasource")!
  if let versionToken = self.versionToken {
    url.appendPathComponent(versionToken)
  }

  URLSession.shared.dataTaskPublisher(for: url)
    .map(\.data)
    .sink(receiveCompletion: { completion in
      if case .failure(let error) = completion {
        print("something went wrong: \(error)")
      }
    }, receiveValue: { [weak self] data in
      guard let self = self
        else { return }

      self.importContext.perform {
        do {
          // 2. Decode the response
          let response = try self.decoder.decode(ImporterResponse.self, from: data)

          // 3. Store the version token
          self.versionToken = response.versionToken

          // 4. Build batch delete requests
          let deletedEventsPredicate = NSPredicate(format: "id IN %@", response.deleted.events)
          let deletedEventsRequest: NSFetchRequest<Event> = Event.fetchRequest()
          deletedEventsRequest.predicate = deletedEventsPredicate
          let batchDeleteEvents = NSBatchDeleteRequest(fetchRequest: deletedEventsRequest)

          let deletedLocationsPredicate = NSPredicate(format: "id IN %@", response.deleted.locations)
          let deletedLocationsRequest: NSFetchRequest<Location> = Location.fetchRequest()
          deletedLocationsRequest.predicate = deletedLocationsPredicate
          let batchDeleteLocations = NSBatchDeleteRequest(fetchRequest: deletedLocationsRequest)

          do {
            // 5. Execute deletions
            try self.importContext.execute(batchDeleteEvents)
            try self.importContext.execute(batchDeleteLocations)

            // 6. Finish import by calling save() on the import context
            try self.importContext.save()
          } catch {
            print("Something went wrong: \(error)")
          }
        } catch {
          print("Failed to decode json: \(error)")
        }
      }

    }).store(in: &cancellables) // store the returned cancellable in a property on `DataImporter`
}

There's a lot of code here but if you follow the comments you'll see that the code I wrote is fairly trivial.

The first step in my code is to construct a valid URL. If we have a versionToken, I append it to the URL. In your app you might have to send your token or timestamp differently, but I'm sure you get the point.

After building the URL a request is made and the response is decoded. My JSON decoder is defined as a lazy var on the DataImporter object as follows:

lazy var decoder: JSONDecoder = {
  let decoder = JSONDecoder()
  decoder.keyDecodingStrategy = .convertFromSnakeCase
  decoder.userInfo[.managedObjectContext] = importContext
  return decoder
}()

Note that I'm using the decoding strategy from my earlier post which means that my JSON response is immediately decoded into NSManagedObject instances using my import context. This means that I don't have to perform any extra work to import the objects from the JSON response into my managed object context since this happens when the JSON is decoded into my managed object. I strongly recommend that you read my earlier post if this seems confusing or weird to you.

Once the response is decoded and the received events and locations are added to my managed object context I can extract the versionToken and store it in UserDefaults for future imports.

The fourth step is to prepare and configure batch delete requests for events and locations to get rid of any events and locations that the server has deleted. A batch delete request takes a regular fetch request and we can use a predicate to make sure only the items that should be deleted actually get deleted.

Lastly, the batch delete requests are executed and we can call save() on the import context to write all the changes we made to the persistent store. If everything was set up properly this will work perfectly and since we defined a mergePolicy on the importContext any conflicts between the old and the new data will be resolved using properties from the object that we're trying to save, which will overwrite the existing data.

While there's a lot to unpack in this flow, it's actually fairly straightforward because we're able to make good use of Core Data's features and a little bit of custom work to make NSManagedObject work with Codable.

In Summary

In this post, you've learned how you can implement a data importing flow that uses Core Data, URLSession and Combine. You saw how a server's response might look when it sends incremental updates, and you learned why this is more convenient than fetching all data everytime your app launches.

Next, I went on to show you how you can set up a Core Data store that applies unique constraints to certain properties of your entities and how you can use a merge policy to resolve conflicts that arise when you attempt to save an object that has an conflicting property. In this article we used NSMergeByPropertyObjectTrumpMergePolicy which will overwrite the stored data with the data from the object that you attempted to store. A perfect policy for a sync like this.

And lastly, I showed you a basic implementation of an import function that performs an import on a background context, deletes any unneeded data and then saves the background context. As long as you set automaticallyMergesChangesFromParent on your viewContext to true, your view contxt will automatically pick up any changes which will in turn trigger an NSFetchedResultController's delegate methods and Core Data will fire the appropriate notifications through NSNotificationCenter.

I hope that this post gave you some valuable insight into how Core Data can be used as a cache for remote data. If you have any questions about this post, or if you've spotted any mistakes, feel free to reach out to me on Twitter. I love hearing from you.

Understanding Swift’s OptionSet

Every once in a while I look at a feature in Swift and I fall down a rabbit hole to explore it so I can eventually write about it. The OptionSet protocol is one of these Swift features.

If you've ever written an animation and passed it a list of options like this, you have already used OptionSet:

UIView.animate(
  withDuration: 0.6, delay: 0, options: [.allowUserInteraction, .curveEaseIn],
  animations: {
    myView.layer.opacity = 0
  }, completion: { _ in })

You may not have realized that you weren't passing an array to the options parameter, and that's not surprising. After all, the options parameter accepts an array literal so it makes a lot of sense to think of the list of options as an array.

But, while it might look like an array, the options parameter accepts an object of type UIView.AnimationOptions which conforms to OptionSet.

Similarly, you may have written something like the following code in SwiftUI:

Rectangle()
  .fill(Color.yellow)
  .edgesIgnoringSafeArea([.leading, .trailing, .bottom])

The edgesIgnoringSafeArea accepts a list of edges that you want to ignore. This list looks a lot like an array since it's passed as an array literal. However, this too is actually an OptionSet.

There are many more examples of where OptionSet is used on Apple's platforms and if you're curious I recommend that you take a look at the documentation for OptionSet under the Relationships section.

In this week's post, I would like to zoom in on what an OptionSet is. And more importantly, I want to zoom in on how an OptionSet works because it's quite an interesting topic.

I'll first show you how you can define a custom OptionSet object and why you would do that. After that, I will explain how an OptionSet works. You will learn about bit shifting and some bitwise operators since that's what OptionSet uses extensively under the hood.

Understanding what an OptionSet is

If you've looked at the documentation for OptionSet already, you may have noticed that it's not terribly complex to create a custom OptionSet. So let's talk about when or why you might want to write an OptionSet of your own before I briefly show you how you can define your own OptionSet objects.

An OptionSet in Swift is a very lightweight object that can be used to represent a fairly large number of boolean values. While you can initialize it with an array literal, it's actually much more like a Set than an array. In fact, OptionSet inherits all of the SetAlgebra that you can apply to sets which means that OptionSet has methods like intersection, union, contains, and several other methods that you might have used on Set.

In the examples I've shown you in the introduction of this post, the OptionSets that were used represented a somewhat fixed set of options that we could either turn off or on. When a certain option is present in the OptionSet we want that option to be on, or true. if we omitted that option we want that option to be ignored. In other words, it should be false.

So when we pass .leading in the list of options for edgesIgnoringSafeArea we want it to be ignored. If we don't pass .leading in the list, we want the view to respect the leading safe area edge because it wasn't present in the list of edges that we want to ignore.

What's interesting about OptionSet in the context of edgesIgnoringSafeArea is that we can also pass .all instead of [.all] if we want to ignore all edges. The reason for this is that OptionSet is an object that can be initialized using an array literal but as I've mentioned before, it is not an array.

Instead, it is an object that stands on its own and it uses a single raw value to represent all options that it holds. Before I explain how that works exactly, let's see how you can define a custom OptionSet because I'm sure that'll provide some useful context.

Defining a custom OptionSet

When you define an OptionSet in Swift, all you do is define a struct (or class) that has a raw value. This raw value can theoretically be any type you want but commonly you will use a type that conforms to FixedWidthInteger, like Int or Int8 because you will get a lot of functionality for free that way (like the conformance to SetAlgebra) and it simply makes more sense.

Next, you should define your options where each option has a unique raw value that's a power of two.

Let's look at an example:

struct NotificationOptions: OptionSet {
  static let daily = NotificationOptions(rawValue: 1)
  static let newContent = NotificationOptions(rawValue: 1 << 1)
  static let weeklyDigest = NotificationOptions(rawValue: 1 << 2)
  static let newFollows = NotificationOptions(rawValue: 1 << 3)

  let rawValue: Int8
}

Looks simple enough right? But what does that << do you might ask. I'm glad you asked and I will talk about it in the next section. Just trust me when I say that the raw values for my options are 1, 2, 4, and 8.

If you'd define this OptionSet in your code you might use it a little bit like this:

class User {
  var notificationPreferences: NotificationOptions = []
}

let user = User()
user.notificationPreferences = [.newContent, .newFollows]

user.notificationPreferences.contains(.newContent) // true
user.notificationPreferences.contains(.weeklyDigest) // false

user.notificationPreferences.insert(.weeklyDigest)

user.notificationPreferences.contains(.weeklyDigest) // true

As you can see you can treat notificationPreferences like a Set even though the type of notificationPreferences is NotificationOptions and your options are represented by a single integer which means that this is an extremely lightweight way to store a set of options that are essentially boolean toggles.

Let's see how this magic works under the hood, shall we?

Understanding how OptionSet works

In the previous section I showed you this OptionSet:

struct NotificationOptions: OptionSet {
  static let daily = NotificationOptions(rawValue: 1)
  static let newContent = NotificationOptions(rawValue: 1 << 1)
  static let weeklyDigest = NotificationOptions(rawValue: 1 << 2)
  static let newFollows = NotificationOptions(rawValue: 1 << 3)

  let rawValue: Int8
}

I told you that the raw values for my options were 1, 2, 4, and 8.

The reason these are my raw values is because I applied a bitshift operator (<<) to the integer 1. Let's take a look at what that means in greater detail.

The integer 1 can be represented in Swift by writing out its bytes as follows:

let one = 0b00000001
print(one == 1) // true

In this case I'm working with an Int8 which uses 8 bits for its storage (you can count the 0s and 1s after 0b to see that there are eight). You can imagine that an Int64 which uses 64 bits as its storage would mean that I have to type a lot of zeroes to represent the full storage in this example.

When we take the integer 1 (or 0b00000001) and apply << 1 to this value, we shift all of its bits to left by one step. This means that the last bit in my integer becomes 0 and the bit that came before the last bit becomes 1 since the last bit shifts leftward by 1. So that means our value is now 0b00000010 which happens to be how the integer two is represented. If apply << 2 to 1, we end up with the following bits: 0b00000100 which happen to be how four is represented. Shifting to left once more would result in the integer eight, and so forth. With a raw value of Int8 we can shift to the left seven times before we reach 0b00000000 and get the integer 0. So that means that an OptionSet with Int8 as its raw value can hold eight options. Int16 can hold sixteen options all the way up to Int64 which will hold up to sixty-four values.

That's a lot of options that can be represented with a single integer!

Now let's see what happens when we add a new static let to represent all options:

static let all: NotificationOptions = [.daily, .newContent, .weeklyDigest, .newFollows]

What's the raw value for all? You know it's not an array of integers since the type of all is NotificationOptions so that list of options must be represented as a single Int8.

If you're curious about the answer, it's 15. But why is that list of options represented as 15 exactly? The simple explanation is that all individual options are added together: 1 + 2 + 4 + 8 = 15. The more interesting explanation is that all options are added together using a bitwise OR operation.

A bitwise OR can be performed using the | operator in Swift:

print(1 | 2 | 4 | 8) // 15

A bitwise OR compares all the bits in each integer and whenever it encounters a bit that's set to 1, it's set to 1 in the final result. Let's look at this by writing out the bits again:

0b00000001 // 1
0b00000010 // 2
0b00000100 // 4
0b00001000 // 8
---------- apply bitwise OR
0b00001111 // 15

If you want to write this out in a Playground, you can use the following:

print(0b00000001 | 0b00000010 | 0b00000100 | 0b00001000 == 0b00001111) // true

Pretty cool, right?

With this knowledge we can try to understand how contains and insert work. Let's look at insert first because that's the simplest one to explain since you already know how that works.

An insert would simply bitwise OR another value onto the current value. Let's use the following code as a starting point:

class User {
  var notificationPreferences: NotificationOptions = []
}

let user = User()
user.notificationPreferences = [.newContent, .newFollows]

In this code we use two options which can be represented as follows: 0b00000010 | 0b00001000. This results in 0b00001010 meaning that we have a raw value of 10. If we then insert a new option, for example .daily, the OptionSet will simply take that raw value of 10 and bitwise OR the new option on top: 0b00001010 | 0b00000001 which means we get 0b00001011 which equals eleven.

To check whether an OptionSet contains a specific option, we need to use another bitwise operator; the &.

The & bitwise operator, or bitwise AND compares two values and sets any bits that are 1 in both values to 1. All other bits are 0. Let's look at an example based on the code from before again:

user.notificationPreferences = [.newContent, .newFollows]

You know that the notificationPreferences's raw value is 10 and that we can represent that as 0b00001010. So let's use the bitwise AND to see if 0b00001010 contains the .newContent option:

0b00001010 // the option set
0b00000010 // the option that we want to find
---------- apply bitwise AND
0b00000010 // the result == the option we want to find

Because the result of applying the bitwise AND equals the value we were trying to find, we know that the option set contains the option we were looking for. Let's look at another example where we check if 0b00001010 contains the weeklyDigest option:

0b00001010 // the option set
0b00000100 // the option that we want to find
---------- apply bitwise AND
0b00000000 // the result == 0

Since the bits that we wanted to find weren't present in the option set, the output is 0 since all bits are 0 in the result of our operation.

With this knowledge you can also perform more complicated SetAlgebra operations. For example, at the start of this article I mentioned that OptionSet has a union method that's provided by SetAlgebra. The union method returns the combination of two OptionSet objects. We can easily calculate this using a bitwise OR operator. Let's assume that we have two OptionSet objects:

let left: NotificationOptions = [.weeklyDigest, .newFollows]
let right: NotificationOptions = [.newContent, .weeklyDigest]

We can calculate the union using left.union(right) which would give an OptionSet that contains weeklyDigest, newFollows and newContent, but let's see how we can calculate this union ourselves using the bitwise OR:

0b00001100 // weeklyDigest and newFollows
0b00000110 // newContent and weeklyDigest
---------- apply bitwise OR
0b00001110 // newContent, weeklyDigest, and newFollows

While you don't have to understand how all of these bitwise operations work, I do think it's very valuable to have this knowledge, even if it's just to help you see how nothing is truly magic, and everything can be explained.

The key information here isn't that you can do bit shifting in Swift, or that you can apply bitwise operators. That's almost a given for any programming language.

The important information here is that OptionSet can store a tremendous amount of information in a single integer with just a couple of bits while also providing a very powerful and flexible API on top of this storage.

While I haven't had to define my own OptionSets often, it's very useful to understand how you can define them, you never know when you might run into a case where you need a flexible, lightweight storage object like OptionSet provides.

In Summary

I was planning to write a short and consise article on OptionSet at first. But then I found more and more interesting concepts to explain, and while there still is more to explain I think this article should provide you with a very good understanding of OptionSet and a couple of Swift's bitwise operators. There are more bitwise operators available in Swift and I highly recommend that you go ahead and explore them.

For now, you saw how to use, and define OptionSet objects. You also saw how an OptionSet's underlying storage works, and you learned that while you can express an OptionSet as an array literal, they are nothing like an array. You now know that a list of different options can be fully represented in an integer.

If you have any questions about this article, or if you have any feedback for me, I would love to hear from you on Twitter.

Fetching objects from Core Data in a SwiftUI project

When you've added Core Data to your SwiftUI project and you have some data stored in your database, the next hurdle is to somehow fetch that data from your Core Data store and present it to the user.

In this week's post, I will present two different ways that you can use to retrieve data from Core Data and present it in your SwiftUI application. By the end of this post you will be able to:

  • Fetch data using the @FetchRequest property wrapper
  • Expose data to your SwiftUI views with an observable object and an @Published property.

Since @FetchRequest is by far the simplest approach, let's look at that first.

Fetching data with the @FetchRequest property wrapper

The @FetchRequest property wrapper is arguably the simplest way to fetch data from Core Data in a SwiftUI view. Several flavors of the @FetchRequest property wrapper are available. No matter the flavor that you use, they all require that you inject a managed object context into your view's environment. Without going into too much detail about how to set up your Core Data stack in a SwiftUI app (you can read more about that in this post) or explaining what the environment is in SwiftUI and how it works, the idea is that you assign a managed object context to the \.managedObjectContext keypath on your view's environment.

So for example, you might write something like this in your App struct:

@main
struct MyApplication: App {

  // create a property for your persistent container / core data abstraction

  var body: some Scene {
    WindowGroup {
      MainView()
        .environment(\.managedObjectContext, persistentContainer.viewContext)
    }
  }
}

When you assign the managed object context to the environment of MainView like this, the managed object context is available inside of MainView and it's automatically passed down to all of its child views.

Inside of MainView, you can create a property that's annotated with @FetchRequest to fetch objects using the managed object context that you injected into MainView's environment. Note that not setting a managed object context on the view's environment while using @FetchRequest will result in a crash.

Let's look at a basic example of @FetchRequest usage:

struct MainView: View {
  @FetchRequest(
    entity: TodoItem.entity(),
    sortDescriptors: [NSSortDescriptor(key: "dueDate", ascending: true)]
  ) var items: FetchedResults<TodoItem>

  var body: some View {
    List(items) { item in
      Text(item.name)
    }
  }
}

The version of the @FetchRequest property wrapper takes two arguments. First, the entity description for the object that you want to fetch. You can get a managed object's entity description using its static entity() method. We also need to pass sort descriptors to make sure our fetched objects are sorted property. If you want to fetch your items without sorting them, you can pass an empty array.

The property that @FetchRequest is applied to has FetchedResults<TodoItem> as its type. Because FetchedResults is a collection type, you can use it in a List the same way that you would use an array.

What's nice about @FetchRequest is that it will automatically refresh your view if any of the fetched objects are updated. This is really nice because it saves you a lot of work in applications where data changes often.

If you're familiar with Core Data you might wonder how you would use an NSPredicate to filter your fetched objects with @FetchRequest. To do this, you can use a different flavor of @FetchRequest:

@FetchRequest(
  entity: TodoItem.entity(),
  sortDescriptors: [NSSortDescriptor(key: "dueDate", ascending: true)],
  predicate: NSPredicate(format: "dueDate < %@", Date.nextWeek() as CVarArg)
) var tasksDueSoon: FetchedResults<TodoItem>

This code snippet passes an NSPredicate to the predicate argument of @FetchRequest. Note that it uses a static method nextWeek() that I defined on Date myself.

I'm sure you can imagine that using @FetchRequest with more complex sort descriptors and predicates can get quite wieldy, and you might also want to have a little bit of extra control over your fetch request. For example, you might want to set up relationshipKeyPathsForPrefetching to improve performance if your object has a lot of relationships to other objects.

Note:
You can learn more about relationship prefetching and Core Data performance in this post.

You can set up your own fetch request and pass it to @FetchRequest as follows:

// a convenient extension to set up the fetch request
extension TodoItem {
  static var dueSoonFetchRequest: NSFetchRequest<TodoItem> {
    let request: NSFetchRequest<TodoItem> = TodoItem.fetchRequest()
    request.predicate = NSPredicate(format: "dueDate < %@", Date.nextWeek() as CVarArg)
    request.sortDescriptors = [NSSortDescriptor(key: "dueDate", ascending: true)]

    return request
  }
}

// in your view
@FetchRequest(fetchRequest: TodoItem.dueSoonFetchRequest)
var tasksDueSoon: FetchedResults<TodoItem>

I prefer this way of setting up a fetch request because it's more reusable, and it's also a lot cleaner when using @FetchRequest in your views.

While you can fetch data from Core Data with @FetchRequest just fine, I tend to avoid it in my apps. The main reason for this is that I've always tried to separate my Core Data code from the rest of my application as much as possible. This means that my views should be as unaware of Core Data as they can possibly be. Unfortunately, @FetchRequest by its very definition is incompatible with this approach. Views must have access to a managed object context in their environment and the view manages an object that fetches data directly from Core Data.

Luckily, we can use ObservableObject and the @Published property wrapper to create an object that fetches objects from Core Data, exposes them to your view, and updates when needed.

Building a Core Data abstraction for a SwiftUI view

There is more than one way to build an abstraction that fetches data from Core Data and updates your views as needed. In this section, I will show you an approach that should fit common use cases where the only prerequisite is that you have a property to sort your fetched objects on. Usually, this shouldn't be a problem because an unsorted list in Core Data will always come back in an undefined order which, in my experience, is not desirable for most applications.

The simplest way to fetch data using a fetch request while responding to any changes that impact your fetch request's results is to use an NSFetchResultsController. While this object is commonly used in conjunction with table views and collection views, we can also use it to drive a SwiftUI view.

Let's look at some code:

class TodoItemStorage: NSObject, ObservableObject {
  @Published var dueSoon: [TodoItem] = []
  private let dueSoonController: NSFetchedResultsController<TodoItem>

  init(managedObjectContext: NSManagedObjectContext) {
    dueSoonController = NSFetchedResultsController(fetchRequest: TodoItem.dueSoonFetchRequest,
    managedObjectContext: managedObjectContext,
    sectionNameKeyPath: nil, cacheName: nil)

    super.init()

    dueSoonController.delegate = self

    do {
      try dueSoonController.performFetch()
      dueSoon = dueSoonController.fetchedObjects ?? []
    } catch {
      print("failed to fetch items!")
    }
  }
}

extension TodoItemStorage: NSFetchedResultsControllerDelegate {
  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    guard let todoItems = controller.fetchedObjects as? [TodoItem]
      else { return }

    dueSoon = todoItems
  }
}

While there's a bunch of code in the snippet above, the contents are fairly straightforward. I created an ObservableObject that has an @Published property called dueSoon. This is the item that a SwiftUI view would use to pull data from.

Note that my TodoItemStorage inherits from NSObject. This is required by the NSFetchedResultsControllerDelegate protocol that I'll talk about in a moment.

In the initializer for TodoItemStorage I create an instance of NSFetchedResultsController. I also assign a delegate to my fetched results controller so we can respond to changes, and I call performFetch to fetch the initial set of data. Next, I assign the fetched results controller's fetched objects to my dueSoon property. The fetchedObject property of a fetched results controller holds all of the managed objects that it retrieved for our fetch request.

The controllerDidChangeContent method in my extension is an NSFetchedResultsControllerDelegate method that's called whenever the fetched results controller changes its content. This method is called whenever the fetched results controller adds, removes, or updates any of the items that it fetched. By assigning the fetched result controller's fetchedObjects to dueSoon again, the @Published property is updated and your SwiftUI view is updated.

Let's see how you would use this TodoItemStorage in an application:

@main
struct MyApplication: App {

  let persistenceManager: PersistenceManager
  @StateObject var todoItemStorage: TodoItemStorage

  init() {
    let manager = PersistenceManager()
    self.persistenceManager = manager

    let managedObjectContext = manager.persistentContainer.viewContext
    let storage = TodoItemStorage(managedObjectContext: managedObjectContext)
    self._todoItemStorage = StateObject(wrappedValue: storage)
  }

  var body: some Scene {
    WindowGroup {
      MainView(todoItemStorage: todoItemStorage)
    }
  }
}

Before we look at what MainView would look like in this example, let's talk about the code in this snippet. I'm using a PersistenceManager object in this example. To learn more about this object and what it does, refer back to an earlier post I wrote about using Core Data in a SwiftUI 2.0 application.

Note that the approach I'm using in this code works for iOS 14 and above. However, the principle of this code applies to iOS 13 too. You would only initialize the TodoItemStorage in your SceneDelegate and pass it to your MainView from there rather than making it an @StateObject on the App struct.

In the init for MyApplication I create my PersistenceManager and extract a managed object context from it. I then create an instance of my TodoItemStorage, and I wrap it in a StateObject. Unfortunately, we can't assign values to an @StateObject directly so we need to use the _ prefixed property and assign it an instance of StateObject.

Lastly, in the body I create MainView and pass it the todoItemStorage.

Let's look at MainView:

struct MainView: View {
  @ObservedObject var todoItemStore: TodoItemStorage

  var body: some View {
    List(todoItemStore.dueSoon) { item in
      return Text(item.name)
    }
  }
}

Pretty lean, right? All MainView knows is that it has a reference to an instance of TodoItemStorage which has an @Published property that exposes todo items that are due soon. It doesn't know about Core Data or fetch requests at all. It just knows that whenever TodoItemStorage changes, it should re-render the view. And because TodoItemStorage is built on top of NSFetchedResultsController we can easily update the dueSoon property when needed.

While this approach is going to work fine, it does sacrifice some of the optimizations that you get with NSFetchedResultsController. For example, NSFetchedResultsController frees up memory whenever it can by only keeping a certain number of objects in memory and (re-)fetching objects as needed. The wrapper I created does not have such an optimization and forces the fetched results controller to load all objects into memory, and they are then kept in memory by the @Published property. In my experience this shouldn't pose problems for a lot of applications but it's worth pointing out since it's a big difference with how NSFetchedResultsController works normally.

While the approach in this post might not suit all applications, the general principles are almost universally applicable. If an NSFetchedResultsController doesn't work for your purposes, you could listen to Core Data related notifications in NotificationCenter yourself and perform a new fetch request if needed. This can all be managed from within your storage object and shouldn't require any changes to your view code.

In my opinion, this is one of the powers of hiding Core Data behind a simple storage abstraction.

In Summary

In this week's post, we took a look at fetching objects from Core Data in a SwiftUI application. First, you learned about the built-in @FetchRequest property wrapper and saw several different ways to use it. You also learned that this property wrapper creates a tight coupling between your views and Core Data which, in my opinion, is not great.

After that, you saw an example of a small abstraction that hides Core Data from your views. This abstraction is built on top of NSFetchedResultsContoller which is a very convenient way to fetch data using a fetch request, and receive updates whenever the result of the fetch request changes. You saw that you can update an @Published property whenever the fetched results controller changes its contents. The result of doing this was a view that is blissfully unaware of Core Data and fetch requests.

If you have any questions about this post, or if you have feedback for me, don't hesitate to reach out to me on Twitter.

Using Codable with Core Data and NSManagedObject

If you've ever wanted to decode a bunch of JSON data into NSManagedObject instances you've probably noticed that this isn't a straightforward exercise. With plain structs, you can conform your struct to Codable and you convert the struct from and to JSON data automatically.

For an NSManagedObject subclass it's not that easy.

If your Core Data data model is configured to automatically generate your entity class definitions for you (which is the default), you may have tried to write the following code to conform your managed object to Decodable:

extension MyManagedObject: Decodable { }

If you do this, the compiler will tell you that it can't synthesize an implementation for init(from:) for a class that's defined in a different file. Xcode will offer you some suggestions like adding an initializer, marking it as convenience and eventually the errors will point you towards making your init required too, resulting in something like the following:

extension MyManagedObject: Decodable {
  required convenience public init(from decoder: Decoder) throws {
  }
}

Once you've written this you'll find that Xcode still isn't happy and that it presents you with the following error:

'required' initializer must be declared directly in class 'MyManagedObject' (not in an extension)

In this week's post, you will learn how you can manually define your managed object subclass and add support for Swift's JSON decoding and encoding features by conforming your managed object to Decodable and Encodable. First, I'll explain how you can tweak automatic class generation and define your managed object subclasses manually while still generating the definition for all of your entity's properties.

After that, I'll show you how to conform your managed object to Decodable, and lastly, we'll add conformance for Encodable as well to make your managed object conform to the Codable protocol (which is a combined protocol of Decodable and Encodable).

Tweaking your entity's code generation

Since we need to define our managed object subclass ourselves to add support for Codable, you need to make some changes to how Xcode generates code for you.

Open your xcdatamodeld file and select the entity that you want to manually define the managed object subclass for. In the sidebar on the right, activate the Data model inspector and set the Codegen dropdown to Category/Extension. Make sure that you set Module to Current product module and that Name is set to the name of the managed object subclass that you will define. Usually, this class name mirrors the name of your entity (but it doesn't have to).

After setting up your data model, you can define your subclasses. Since Xcode will generate an extension that contains all of the managed properties for your entity, you only have to define the classes that Xcode should extend:

class TodoItem: NSManagedObject {
}

class TodoCompletion: NSManagedObject {
}

Once you've defined your managed object subclasses, Xcode generates extensions for these classes that contain all of your managed properties while giving you the ability to add the required initializers for the Decodable and Encodable protocols.

Let's add conformance for Decodable first.

Conforming an NSManagedObject to Decodable

The Decodable protocol is used to convert JSON data into Swift objects. When your objects are relatively simple and closely mirror the structure of your JSON, you can conform the object to Decodable and the Swift compiler generates all the required decoding code for you.

Unfortunately, Swift can't generate this code for you when you want to make your managed object conform to Decodable.

Because Swift can't generate the required code, we need to define the init(from:) initializer ourselves. We also need to define the CodingKeys object that defines the JSON keys that we want to use when decoding JSON data. Adding the initializer and CodingKeys for the objects from the previous section looks as follows:

class TodoCompletion: NSManagedObject, Decodable {
  enum CodingKeys: CodingKey {
    case completionDate
  }

  required convenience init(from decoder: Decoder) throws {
  }
}

class TodoItem: NSManagedObject, Decodable {
  enum CodingKeys: CodingKey {
    case id, label, completions
  }

  required convenience init(from decoder: Decoder) throws {
  }
}

Before I get to the decoding part, we need to talk about managed objects a little bit more.

Managed objects are always associated with a managed object context. When you want to create an instance of a managed object you must pass a managed object context to the initializer.

When you're initializing your managed object with init(from:) you can't pass the managed object context along to the initializer directly. And since Xcode will complain if you don't call self.init from within your convenience initializer, we need a way to make a managed object context available within init(from:) so we can properly initialize the managed object.

This can be achieved through JSONDecoder's userInfo dictionary. I'll show you how to do this first, and then I'll show you what this means for the initializer of TodoItem from the code snippet I just showed you. After that, I will show you what TodoCompletion ends up looking like.

Since all keys in JSONDecoder's userInfo must be of type CodingUserInfoKey we need to extend CodingUserInfoKey first to create a managed object context key:

extension CodingUserInfoKey {
  static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")!
}

We can use this key to set and get a managed object context from the userInfo dictionary. Now let's create a JSONDecoder and set its userInfo dictionary:

let decoder = JSONDecoder()
decoder.userInfo[CodingUserInfoKey.managedObjectContext] = myPersistentContainer.viewContext

When we use this instance of JSONDecoder to decode data, the userInfo dictionary is available within the initializer of the object we're decoding to. Let's see how this works:

enum DecoderConfigurationError: Error {
  case missingManagedObjectContext
}

class TodoItem: NSManagedObject, Decodable {
  enum CodingKeys: CodingKey {
    case id, label, completions
 }

  required convenience init(from decoder: Decoder) throws {
    guard let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else {
      throw DecoderConfigurationError.missingManagedObjectContext
    }

    self.init(context: context)

    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.id = try container.decode(Int64.self, forKey: .id)
    self.label = try container.decode(String.self, forKey: .label)
    self.completions = try container.decode(Set<TodoCompletion>.self, forKey: .completions) as NSSet
  }
}

In the initializer for TodoItem I try to extract the object at CodingUserInfoKey.managedObjectContext from the Decoder's userInfo dictionary and I try to cast it to an NSManagedObjectContext. If this fails I throw an error that I've defined myself because we can't proceed without a managed object context.

After that, I call self.init(context: context) to initialize the TodoItem and associate it with a managed object context.

The last step is to decode the object as you normally would by grabbing a container that's keyed by CodingKeys.self and decoding all relevant properties into the correct types.

Note that Core Data still uses Objective-C under the hood so you might have to cast some Swift types to their Objective-C counterparts like I had to with my Set<TodoCompletion>.

For completion, this is what the full class definition for TodoCompletion would look like:

class TodoCompletion: NSManagedObject, Decodable {
  enum CodingKeys: CodingKey {
    case completionDate
  }

  required convenience init(from decoder: Decoder) throws {
    guard let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else {
      throw DecoderConfigurationError.missingManagedObjectContext
    }

    self.init(context: context)

    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.completionDate = try container.decode(Date.self, forKey: .completionDate)
  }
}

This code shouldn't look surprising; it's basically the same as the code for TodoItem. Note that the decoder that's used to decode the TodoItem is also used to decode TodoCompletion which means that it also has the managed object context in its userInfo dictionary.

If you want to test this code, you can use the following JSON as a starting point:

[
  {
    "id": 0,
    "label": "Item 0",
    "completions": []
  },
  {
    "id": 1,
    "label": "Item 1",
    "completions": [
      {
        "completionDate": 767645378
      }
    ]
  }
]

Unfortunately, it takes quite a bunch of code to make Decodable work with managed objects, but the final solution is something I'm not too unhappy with. I like how easy it is to use once set up properly.

Adding support for Encodable to an NSManagedObject

While we had to do a bunch of custom work to add support for Decodable to our managed objects, adding support for Encodable is far less involved. All we need to do is define encode(to:) for the objects that need Encodable support:

class TodoItem: NSManagedObject, Codable {
  enum CodingKeys: CodingKey {
    case id, label, completions
  }

  required convenience init(from decoder: Decoder) throws {
    // unchanged implementation
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(id, forKey: .id)
    try container.encode(label, forKey: .label)
    try container.encode(completions as! Set<TodoCompletion>, forKey: .completions)
  }
}

Note that I had to convert completions (which is an NSSet) to a Set<TodoCompletion> explicitly. The reason for this is that NSSet isn't Encodable but Set<TodoCompletion> is.

For completion, this is what TodoCompletion looks like with Encodable support:

class TodoCompletion: NSManagedObject, Codable {
  enum CodingKeys: CodingKey {
    case completionDate
  }

  required convenience init(from decoder: Decoder) throws {
    // unchanged implementation
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(completionDate, forKey: .completionDate)
  }
}

Note that there is nothing special that I had to do to conform my managed object to Encodable compared to a normal manual Encodable implementation.

In Summary

In this week's post, you learned how you can add support for Codable to your managed objects by changing Xcode's default code generation for Core Data entities, allowing you to write your own class definitions. You also saw how you can associate a managed object context with a JSONDecoder through its userInfo dictionary, allowing you to decode your managed objects directly from JSON without any extra steps. To wrap up, you saw how to add Encodable support, making your managed object conform to Codable rather than just Decodable.

If you have any questions about this post or if you have feedback for me, don't hesitate to shoot me a message on Twitter.