What does “atomic” mean in programming?

When you're learning about databases or multithreaded programming, it's likely that you'll come across the term "atomic" at some point.

Usually you'll hear the term in the context of an operation. For example, an atomic read / write operation. Or atomic access to a property.

But what does this mean?

Generally, you can summarize atomic as "one at a time".

For example, when accessing or mutating a property is atomic, it means that only one read or write operation can be performed at a time. If you have a program that reads a property atomically, this means that the property cannot change during this read operation.

In Swift, operations on a dictionary are not atomic. This means that in a multithreaded application, a dictionary might be changed from one thread while another thread is reading from it. No thread or operation has exclusive access to your dictionary.

If the operation was atomic, the first read operation would have to finish before the write can start.

Another way to think of an atomic operation is that no observer of an atomic operation can "see" the operation as in-progress. You can observe the operation as not yet started or as completed, but never in between.

I wrote a post about an @Atomic property wrapper that I've seen making the rounds. In that post, you can see why this property wrapper does not guarantee exclusive access properly for value types, resulting in strange results.

If you want to learn more about atomicity and see an example of atomicity in Swift, I highly recommend you give that post a look.

If you're just looking for a definition, think of atomic as exclusive or one at a time. When an operation is performed atomically, you know that no other operations will interfere with your atomic operation.

I hope this quick tip gave you a better idea of what atomic means in programming. If you have any questions about this posts or if you have suggestions to make it better, feel free to reach out on Twitter.

10 things iOS developers should focus on in 2021

I know. This is a clickbaity title. And yes, I know that this list is not relevant for everybody. I know that not every iOS developer has to learn everything on this list.

That said, this list is a list of technologies and skills that I think are either already important, or becoming increasingly important this year.

It's a list of technologies and skills that I have learned, plan to learn, or would like to learn this year.

It's also a list that hopefully inspires you to broaden your horizons, and learn new things. Or maybe this list inspires you to refresh your knowledge of things that you've looked at before but haven't paid attention to for a while.

Disclaimer: I wasn't paid to link to any of the creators that I mention in this post. I also don't receive any kickback for any purchases you make through my links (except for my own books, I do make money off those).

Combine

Apple released the Combine framework with iOS 13. Combine is Apple's functional reactive programming framework that's similar to RxSwift, but also very different. Combine's main selling point is that it's a first-party framework. This means that it will be maintained by Apple (hopefully for a long time), and updated with releases of Apple's OSes which is both great, and a downside.

In any event, my goal isn't to convince you that Combine is great. It's also not my goal to convince you Combine isn't great.

I'll leave it up to you to make up your mind about Combine.

There's no denying that Apple is betting big on Combine, and that it's worth taking a look at it.

Especially because SwiftUI makes heavy use of Combine.

If you want to learn more about Combine, I'd like to recommend my Practical Combine book to help you get up and running.

SwiftUI

I don't think you can talk about iOS development these days without at least mentioning SwiftUI. In fact, I think SwiftUI is quickly becoming more and more important in the land of iOS.

At this point, it's not likely that you'll need to know SwiftUI to be employable in the short term. I do believe that SwiftUI is an important framework to learn, and it can certainly give you an edge when looking for jobs.

Some good resources to look at if you want to learn SwiftUI are Apple's tutorials, Paul Hudson's 100 days of SwiftUI, objc.io's Thinking in SwiftUI, Daniel Steinberg's SwiftUI Kickstart, and Majid Jabrayilov's website.

Of course there are many, many more resources. These are just some of my personal favorites.

Whether SwiftUI is production-ready or not is an interesting discussion at this time. There certainly are rough edges, and we're collectively figuring out how to properly write apps in SwiftUI. A popular architecture for SwiftUI apps that you might want to look at is pointfree.co's Composable Architecture.

XCTest

If there's one thing we all know we should do, but regularly skip, can't do, won't do, or simply forget about, it's unit testing.

My personal motivation to write tests whenever I can is that it allows me to know something works rather than thinking it should still work after making changes some where else in my codebase. Unless I've tested it, I can not be more certain that thinking something works. Automated tests make sure that I never forget to test certain features, and it's much, much faster than testing manually.

If you're struggling to convince your manager that you should be writing tests, take a look at my talk from 2019 called Adopting TDD in the Workplace. The bottom line is that testing should be part of your process as a developer. Tests help you write decoupled code, and once they are set up, your tests run all the time. This is much faster and more rigorous than manual testing will ever be.

If you want to learn more about unit testing on iOS I can highly recommend Jon Reids' website and his book iOS unit testing by example.

Collection Views

Apple has been busy improving Collection Views over the past few years. Especially iOS 13's compositional collection view layout and diffable data sources were huge improvements to how we use collection views.

In iOS 14, Apple has made more improvements. For example, we now use a collection view list layout that's extremely flexible, and there's a new way to register and dequeue custom cells called cell registration.

If you're not familiar with collection views, or if you haven't looked at the new features yet I highly recommend that you do. Apple really did a fantastic job on collection views. Make sure to check out Apple's sample app to see most of the new features since iOS 13 in action.

Core Data

Even though Core Data isn't a new framework and has its root set firmly in the realm of Objective-C, it's still a very relevant technology. Apple has invested significant resources into making Core Data easier and nicer to work with, and they even added the ability to sync with iCloud automatically in iOS 13.

Strictly speaking this wasn't Apple's first attempt to add iCloud syncing to Core Data but it's certainly Apple best attempt at doing this.

If you've used Core Data before Apple added NSPersistentContainer in iOS 10 and didn't like it, or if you were told to avoid Core Data because it's clunky, bad, inefficient, or hard to work with, I highly recommend that you take another look.

Apple has lots of information about Core Data on their website, and community members like Antoine van der Lee have written a lot about Core Data.

I have done a lot of Core Data related writing myself, and I released a book on the framework at the start of this year called Practical Core Data that I personally really like and would highly recommend to newcomers and people that haven't looked at Core Data for a while.

Instruments

We all want our apps to be free of memory leaks, frame drops and other characteristics that are poor for performance.

My favorite way to discover performance issues is the Instruments tool. If you've never looked at Instruments before, I think 2021 should be the year that you change that.

Instruments is a fundamental tool that, in my opinion, deserves a place in every iOS developer's toolbox.

To get started with Instruments, you could take a look at this overview that Apple provides.

If that overview is a bit much, you might like this post I wrote on the Time Profiler which is the Instrument I use most (by far).

Communication Skills

Being able to communicate efficiently as a developer is important. Both verbally, and in written form. In my opinion, we're never done improving how we communicate.

That's why, in 2021 I think it's good to take some time to improve your so-called "soft" skills. This will help you become a better team member, a more efficient communicator, and a better listener.

These are skills that I think developers often underestimate which is why it was important for me to add this to the list. (Thanks for the tip HeidiPuk).

Some resources to help you get started are this talk from Ask Furrow and this interview/podcast episode with Sean Allen and Mayuko.

Practice your communication skills, write often, make sure you listen to people, and ask for feedback on your communication skills when possible. If you do this regularly I'm sure you'll be a much stronger communicator by the end of 2021.

Building Universal Apps

Now this is a technology that I personally want to spend a bunch of time on in 2021. For the past couple of years, Apple has been showing us how to build apps that run on iOS and the Mac. First with catalyst, later with SwiftUI.

Now that Apple's M1 Macs are out and they can run iOS apps natively, I think it's time to start considering the Mac as a platform that we should write our apps for whenever possible. Similar to how we try to make sure most (if not all) of our apps run on iPads as well as iPhones.

Unfortunately I haven't come across any useful resources for this yet. Apple has some WWDC videos that might be interesting, but since I haven't looked at universal apps just yet, I owe you some links.

If you have good universal app resources for me, let me know.

ARKit (and RealityKit)

As rumors of Apple glasses keep growing stronger and stronger, I think it's likely that we'll eventually see them. Maybe in 2021, maybe later.

However, once these glasses are (inevitably) announced, we'll probably want to build apps for them.

My bet is that once we're able to build apps for glasses, we'll do this on top of Apple's Augmented Reality frameworks.

In my opinion, now is a perfect time to start learning ARKit and build some Augmented Reality experiences. Especially if you're interested in possibly making apps for the rumored Apple glasses.

In addition to documentation and WWDC videos for ARKit, Apple provides lots of resources to help you get started with Augmented Reality.

Async / Await

While this feature isn't offcially available in Swift yet, as its bits and pieces are still being reviewed on the Swift forums, I think async / await is one of the biggest new things to focus on this year.

I don't know which Swift version will contain an official async/await release but you can experiment with the feature today if you're using the latest Swift build.

Async/await is going to significantly change how we write asynchronous code in Swift, and I'm super excited about it.

If you want to follow along with its development, you can do so on the Swift forums where all reviews and pitches are published.

In Summary

This list of 10 things you should focus on in 2021 is a list that I think is relevant. Of course, some things might not be relevant for you. Or maybe this list is missing important technologies or skills that you think everybody should focus on.

That's okay, I just hope this list gave you a direction of (new) things to learn. Some of the things on my list have been around for a while, others are brand new. If you don't get around to learning the brand new things this year, that's okay. Learn and investigate at your own pace, focus on what gets you where want to go.

If you have any feedback on this list, or if you want to share your focus for 2021, send me a Tweet. I love hearing from you.

Observing the result of saving a background managed object context with Combine

I love posts where I get to put write about two of my favorite frameworks at the moment; Combine and Core Data.

When you're working with Core Data, it's common to perform save operations asynchronously using a background context. You could even perform an asynchronous save on the main managed object context.

Consider the following method that I added to an object that I wrote called StorageProvider:

public extension StorageProvider {
  func addTask(name: String, description: String,
               nextDueDate: Date, frequency: Int,
               frequencyType: HouseHoldTask.FrequencyType) {

    persistentContainer.performBackgroundTask { context in
      let task = HouseHoldTask(context: context)
      task.name = name
      task.taskDescription = description
      task.nextDueDate = nextDueDate
      task.frequency = Int64(frequency)
      task.frequencyType = Int64(frequencyType.rawValue)

      do {
        try context.save()
      } catch {
        print("Something went wrong: \(error)")
        context.rollback()
      }
    }
  }
}

My StorageProvider has a property called persistentContainer which is an NSPersistentContainer and it contains several useful features like this convenient method to create a new instance of a HouseHoldTask model. The contents and details of this model are not relevant per se.

It's the asynchronous nature of this method that I want you to consider. Note that even if I use persistentContainer.viewContext.perform, the contents of the perform closure are not executed synchronously; addTask returns before the save is completed in both cases.

Now consider the following SwiftUI code:

struct AddTaskView: View {
  // a bunch of properties

  /// Passed in by the parent. When set to false this view is dismissed by its parent
  @Binding var isPresented: Bool

  let storageProvider: StorageProvider

  var body: some View {
    NavigationView {
      Form {
        // A form that's used to configure a task
      }
      .navigationTitle("Add Task")
      .navigationBarItems(leading: Button("Cancel") {
        isPresented = false
      }, trailing: Button("Save") {
        // This is the part I want you to focus on
        storageProvider.addTask(name: taskName, description: taskDescription,
                                nextDueDate: firstOccurrence, frequency: frequency,
                                frequencyType: frequencyType)
        isPresented = false
      })
    }
  }
}

I've omitted a bunch of code in this example and I added a comment that reads This is the part I want you to focus on for the most interesting part of this code.

When the user taps Save, I create a task and dismiss the AddTaskView by setting its isPresented property to false. In my code the view that presents AddTaskView passes a binding to AddTaskView, allowing the parent of AddTaskView to dismiss this view when appropriate.

However, since addTask is asynchronous, we can't respond to any errors that might occur.

If you want to prevent dismissing AddTaskView before the task is saved you would usually use the viewContext to save your managed object using performAndWait. That way your code is run on the viewContext's queue, your code will also wait for the closure passed to performAndWait to complete. That way, you could return a Result<Void, Error> from your addTask method to communicate the result of your save operation to the user.

Usually, a save operation will be quite fast, and running it on the viewContext doesn't do much harm. Of course, there are exceptions where you want your save operation to run in the background to prevent blocking the main thread. And since most save operations will probably succeed, you might even want to allow the UI to continue operating as if the save operation has already succeeded, and show an alert to the user in the (unlikely) scenario that something went wrong. Or maybe you even want to present an alert in case the save operation succeeded.

An interesting way to achieve this is through Combine. You can wrap the Core Data save operation in a Future and use it to update a StateObject in the main view that's responsible for presenting AddTaskView.

I'll show you the updated addTask method first, and then we'll work our way up to addTask from the main view up.

Here's the adjusted addTask method:

public extension StorageProvider {
  func addTask(name: String, description: String,
               nextDueDate: Date, frequency: Int,
               frequencyType: HouseHoldTask.FrequencyType) -> Future<Void, Error> {
    Future { promise in
      self.persistentContainer.performBackgroundTask { context in
        let task = HouseHoldTask(context: context)
        task.name = name
        task.taskDescription = description
        task.nextDueDate = nextDueDate
        task.frequency = Int64(frequency)
        task.frequencyType = Int64(frequencyType.rawValue)

        do {
          try context.save()
          promise(.success(()))
        } catch {
          print("Something went wrong: \(error)")
          promise(.failure(error))
          context.rollback()
        }
      }
    }
  }
}

This setup is fairly straightforward. I create a Future and fulfill it with a success if everything is good and Error if something went wrong. Note that my Output for this Future is Void. I'm not really interested in publishing any values when everything went okay. I'm more interested in failures.

Tip:
If you're not familiar with Combine's Futures, check out my post on using Future in Combine.

Next, let's take a look at the main view in this scenario; TasksOverview. This view has an Add Task button and presents the AddTaskView:

struct TasksOverview: View {
  static let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .long
    return formatter
  }()

  @FetchRequest(fetchRequest: HouseHoldTask.sortedByNextDueDate)
  var tasks: FetchedResults<HouseHoldTask>

  @State var addTaskPresented = false

  // !!
  @StateObject var addTaskResult = AddTaskResult()

  let storageProvider: StorageProvider

  var body: some View {
    NavigationView {
      List(tasks) { (task: HouseHoldTask) in
        VStack(alignment: .leading) {
          Text(task.name ?? "--")
          if let dueDate = task.nextDueDate {
            Text("\(dueDate, formatter: Self.dateFormatter)")
          }
        }
      }
      .listStyle(PlainListStyle())
      .navigationBarItems(trailing: Button("Add new") {
        addTaskPresented = true
      })
      .navigationBarTitle("Tasks")
      .sheet(isPresented: $addTaskPresented, content: {
        // !!
        AddTaskView(isPresented: $addTaskPresented,
                    storageProvider: storageProvider,
                    resultObject: addTaskResult)
      })
      .alert(isPresented: $addTaskResult.hasError) {
        // !!
        Alert(title: Text("Could not save task"),
              message: Text(addTaskResult.error?.localizedDescription ?? "unknown error"),
              dismissButton: .default(Text("Ok")))
      }
    }
  }
}

I added three comments in the code above to the places where you should focus your attention. First, I create an @StateObject that holds an AddTaskResult object. I will show you this object in a moment but it'll be used to determine if we should show an error alert and it holds information about the error that occurred.

The second comment I added shows where I initialize my AddTaskView and you can see that I pass the addTaskResult state object to this view.

The third and last comment shows how I present the error alert.

For posterity, here's what AddTaskResult looks like:

class AddTaskResult: ObservableObject {
  @Published var hasError = false
  var error: Error?
}

It's a simple object with a simple published property that's used to determine whether an error alert should be shown.

Now all we need is a way to link together the Future that's created in addTask and the TasksOverview which will show an alert if needed. This glue code is written in the AddTaskView.

struct AddTaskView: View {
  // this is all unchanged

  // a new property to hold AddTaskResult
  @ObservedObject var resultObject: AddTaskResult

  var body: some View {
    NavigationView {
      Form {
        // form to create a task
      }
      .navigationTitle("Add Task")
      .navigationBarItems(leading: Button("Cancel") {
        isPresented = false
      }, trailing: Button("Save") {
        // this is where it gets interesting
        storageProvider.addTask(name: taskName, description: taskDescription,
                                nextDueDate: firstOccurrence, frequency: frequency,
                                frequencyType: frequencyType)
          .map { return false }
          .handleEvents(receiveCompletion: { completion in
            if case let .failure(error) = completion {
              self.resultObject.error = error
            }
          })
          .replaceError(with: true)
          .receive(on: DispatchQueue.main)
          .assign(to: &resultObject.$hasError)

        // this view is still dismissed as soon as Save is tapped
        isPresented = false
      })
    }
  }
}

In the code above the most important differences are that AddTaskView now has a resultObject property, and I've added some Combine operators after addTask.

Since addTask now returns a Future, we can apply operators to this Future to transform its output. First, I map the default Void output to false. This means that no errors occurred. Then I use a handleEvents operator with a receiveCompletion closure. This allows me to intercept errors and assign the intercepted error to the resultObject's error property so it can be used in TasksOverviewView later.

Next, I replace any errors that may have occurred with true which means that an error occurred. Since all UI mutations in SwiftUI must originate on the main thread I use receive(on:) to ensure that the operator that follows it will run on the main thread.

Lastly, I use Combine's assign(to:) subscriber to assign the transformed output (a Bool) of the Future to &resultObject.$hasError. This will modify the TasksOverview's @StateObject and trigger my alert to be shown if hasError was set to true.

Because I use an object that is owned by TasksOverview in my assign(to:) the subscription to my Future is kept alive even after AddTaskView is dismissed. Pretty neat, right?

In Summary

In this post, you saw an example of how you can wrap an asynchronous operation, like saving a background managed object context, in a Combine Future. You saw how you can use @StateObject in SwiftUI to determine if an when an error should be presented, and you saw how you can wire everything up so a Core Data save operation ultimately mutates a property on your state object to present an alert.

Complex data flows like these are a lot of fun to play with, and Combine is an incredibly useful tool when you're dealing with situations like the one I described in this article.

If you have any questions about this article, or if you have any feedback for me, don't hestitate to send me a message on Twitter.

Responding to changes in a managed object context

Working with multiple managed object contexts will often involve responding to changes that were made in one context to update another context. You might not even want to update another context but reload your UI or perform some other kind of update. Maybe you want to do this when a specific context updates, or maybe you want to run some code when any context updates.

In this week's post I will show you how you can listen for changed in managed object contexts, and how you can best use them. I will also show you a convenient way to extract information from a Core Data related Notification object through a nice extension.

Subscribing to Core Data related Notifications

Regardless of your specific needs, Core Data has a mechanism that allows you to be notified when a managed object updates. This mechanism plays a key role in objects like NSFetchedResultsController which tracks a specific managed object context in order to figure out whether specific objects were inserted, deleted or updated. In addition to this, a fetch result also tracks whether the position of a managed object within a result set has changed which is not something that you can trivially track yourself; this is implemented within the fetched results controller.

You can monitor and respond to changes in your managed object contexts through NotificationCenter. When your managed object context updates or saves, Core Data will post a notification to the default NotificationCenter object.

For example, you can listen for an NSManagedObjectContext.didSaveObjectsNotification to be notified when a managed object context was saved:

class ExampleViewModel {
  init() {
    let didSaveNotification = NSManagedObjectContext.didSaveObjectsNotification
    NotificationCenter.default.addObserver(self, selector: #selector(didSave(_:)),
                                            name: didSaveNotification, object: nil)
  }

  @objc func didSave(_ notification: Notification) {
    // handle the save notification
  }
}

The example code above shows how you can be notified when any managed object context is saved. The notification you receive here contains a userInfo dictionary that will tell you which objects were inserted, deleted and/or updated. For example, the following code extracts the inserted objects from the userInfo dictionary:

@objc func didSave(_ notification: Notification) {
  // handle the save notification
  let insertedObjectsKey = NSManagedObjectContext.NotificationKey.insertedObjects.rawValue
  print(notification.userInfo?[insertedObjectsKey])
}

Note that NSManagedObjectContext has a nested type called NotificationKey. This type is an enum that has cases for every relevant key that you might want to use. Since the enum case name for the notification keys don't match with the string that you need to access the relevant key in the dictionary, it's important that you use the enum's rawValue rather than the enum case directly.

Note that NSManagedObjectContext.NotificationKey is only available on iOS 14.0 and up. For iOS 13.0 and below you can use the Notification.Name.NSManagedObjectContextDidSave to listen for save events. For a more complete list for iOS 13.0 notifications I'd like to point you to the "See Also" section on the documentation page for NSManagedObjectContextDidSave which is located here.

I'm not a big fan of how verbose this is so I like to use an extension on Dictionary to help me out:

extension Dictionary where Key == AnyHashable {
  func value<T>(for key: NSManagedObjectContext.NotificationKey) -> T? {
    return self[key.rawValue] as? T
  }
}

This extension is very simple but it allows me to write code from before as follows which is much cleaner:

@objc func didSave(_ notification: Notification) {
  // handle the save notification
  let inserted: Set<NSManagedObject>? = notification.userInfo?.value(for: .insertedObjects)
  print(inserted)
}

We could take this even further with an extension on Notfication specifically for Core Data related notifications:

extension Notification {
  var insertedObjects: Set<NSManagedObject>? {
    return userInfo?.value(for: .insertedObjects)
  }
}

This notification would be used as follows:

@objc func didSave(_ notification: Notification) {
  // handle the save notification
  let inserted = notification.insertedObjects
  print(inserted)
}

I like how clean the callsite is here . The main downside is that we can't constrain the extension to Core Data related notifications only, and we'll need to manually add computed properties for every notification key. For example, to extract all updated objects through a Notification extension you'd have to add the following property to the extension I showed you earlier:

var updatedObjects: Set<NSManagedObject>? {
  return userInfo?.value(for: .updatedObjects)
}

It's not a big deal to add these computed properties manually, and it can clean up your code quite a bit so it's worth the effort in my opinion. Whether you want to use an extension like this is really a matter of preference so I'll leave it up to you to decide whether you think this is a good idea or not.

Let's get bakc on topic, this isn't a section about building convenient extensions after all. It's about observing managed object context changes.

The code I showed you earlier subscribed to the NSManagedObjectContext.didSaveObjectsNotification in a way that would notify you every time any managed object context would save. You can constrain this to a specific notification as follows:

let didSaveNotification = NSManagedObjectContext.didSaveObjectsNotification
let targetContext = persistentContainer.viewContext
NotificationCenter.default.addObserver(self, selector: #selector(didSave(_:)),
                                        name: didSaveNotification, object: targetContext)

By passing a reference to a managed object context you can make sure that you're only notified when a specific managed object context was saved.

Imagine that you have two managed object contexts. A viewContext and a background context. You want to update your UI whenever one of your background contexts saves, triggering a change in your viewContext. You could subscribe to all managed object context did save notifications and simply update your UI when any context got saved.

This would work fine if you have set automaticallyMergesChangesFromParent on your viewContext to true. However, if you've set this property to false you find that your viewContext might not yet have merged in the changes from the background context which means that updating your UI will not always show the lastest data.

You can make sure that a managed object context merges changes from another managed object context by subscribing to the didSaveObjectsNotification and merging in any changes that are contained in the received notification as follows:

@objc func didSave(_ notification: Notification) {
  persistentContainer.viewContext.mergeChanges(fromContextDidSave: notification)
}

Calling mergeChanges on a managed object context will automatically refresh any managed objects that have changed. This ensures that your context always contains all the latest information. Note that you don't have to call mergeChanges on a viewContext when you set its automaticallyMergesChangesFromParent property to true. In that case, Core Data will handle the merge on your behalf.

In addition to knowing when a managed object context has saved, you might also be interested in when its objects changed. For example, because the managed object merged in changes that were made in another context. If this is what you're looking for, you can subscribe to the didChangeObjectsNotification.

This notification has all the same characteristics as didSaveObjectsNotification except it's fired when a context's objects change. For example when it merges in changes from another context.

The notifications that I've shown you so far always contain managed objects in their userInfo dictionary, this provides you full access to the changed objects as long as you access these objects from the correct managed object context.

This means that if you receive a didSaveObjectsNotification because a context got saved, you can only access the included managed objects on the context that generated the notification. You could manage this by extracting the appropriate context from the notifiaction as follows:

@objc func didSave(_ notification: Notification) {
  guard let context = notification.object as? NSManagedObjectContext,
        let insertedObjects = notification.insertedObjects as? Set<ToDoItem> else {
    return
  }
  context.perform {
    for object in insertedObjects {
      print(object.dueDate)
    }
  }
}

While this works, it's not always appropriate.

For example, it could make perfect sense for you to want to access the inserted objects on a different managed object context for a variety of reasons.

Extracting managed object IDs from a notification

When you want to pass managed objects from a notification to a different context, you could of course extract the managed object IDs and pass them to a different context as follows:

@objc func didSave(_ notification: Notification) {
  guard let insertedObjects = notification.insertedObjects else {
    return
  }

  let objectIDs = insertedObjects.map(\.objectID)

  for id in objectIDs {
    if let object = try? persistentContainer.viewContext.existingObject(with: id) {
      // use object in viewContext, for example to update your UI
    }
  }
}

This code works, but we can do better. In iOS 14 it's possible to subscribe to Core Data's notifications and only receive object IDs. For example, you could use the insertedObjectIDs notification to obtain all new object IDs that were inserted.

The Notification extension property to get convenient access to insertedObjectIDs would look as follows:

extension Notification {
  // other properties

  var insertedObjectIDs: Set<NSManagedObjectID>? {
    return userInfo?.value(for: .insertedObjectIDs)
  }
}

You would then use the following code to extract managed object IDs from the notification and use them in your viewContext:

@objc func didSave(_ notification: Notification) {
  guard let objectIDs = insertedObjects.insertedObjectIDs else {
    return
  }

  for id in objectIDs {
    if let object = try? persistentContainer.viewContext.existingObject(with: id) {
      // use object in viewContext, for example to update your UI
    }
  }
}

It doesn't save you a ton of code but I do like that this notification is more explicit in its intent than the version that contains full managed objects in its userInfo.

In Summary

Notifications can be an incredibly useful tool when you're working with any number of managed object contexts, but I find them most useful when working with multiple managed object contexts. In most cases you'll be interested in the didChangeObjectsNotification for the viewContext only. The reason for this is that it's often most useful to know when your viewContext has merged in data that may have originated in another context. Note that didChangeObjectsNotification also fires when you save a context.

This means that when you subscribe to didChangeObjectsNotification on the viewContext and you insert new objects into the viewContext and then call save(), the didChangeObjectsNotification for your viewContext will fire.

When you use NSFetchedResultsController or SwiftUI's @FetchRequest you may not need to manually listen for notifications often. But it's good to know that these notifications exist, and to understand how you can use them in cases where you're doing more complex and custom work.

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

Building a concurrency-proof token refresh flow in Combine

Refreshing access tokens is a common task for many apps that use OAuth or other authentication mechanisms. No matter what your authentication mechanism is, your tokens will expire (eventually) and you'll need to refresh them using a refresh token. Frameworks like RxSwift and Combine provide convenient ways to build pipelines that perform transformation after transformation on a succesful network response, allowing you to grab Data, manipulate and transform it to an instance of a model object or anything else.

Programming the not-so-happy path where you need to refresh a token is not as simple. Especially because in an ideal world you only fire a single token refresh request even if multiple requests fail due a token error at the same time. You'll want to retry every request as soon as possible without firing more than one token refresh call.

The trick to building something like this is partly a Combine problem (what type of publisher can/should we use) but mostly a concurrency problem (how do we ensure that we only perform a single network call).

In this week's post I'll take a closer look at this problem and show a solution that should be able to hold up even if you're hammering it with token refresh requests.

Setting up a simple networking layer

In this post I will set up a simple mock networking layer that will allow you to experiment with the solution provided in this post even if you don't have a back-end or a token that requires refreshing. I'll start by showing you my models:

struct Token: Decodable {
  let isValid: Bool
}

struct Response: Decodable {
  let message: String
}

enum ServiceErrorMessage: String, Decodable, Error {
  case invalidToken = "invalid_token"
}

struct ServiceError: Decodable, Error {
  let errors: [ServiceErrorMessage]
}

These are just a few simple models. The Token is the key actor here since it's used to authenticate network calls. The Response object models a succesful request and ServiceError and ServiceErrorMessage represent the response that we'll get in case a user isn't authenticated due to a bad or expired token. You back-end will probably return something entirely different and your Token will probably have an expiresAt or expiresIn field that you would use to determine if the current device clock is past the token's expected expiration date. Since different servers might use different mechanisms to let their client know about a token's moment of expiration I won't detail that here. Just make sure that your version of isValid is based on the token's expiration time.

The networking itself is modelled by this protocol:

protocol NetworkSession: AnyObject {
  func publisher(for url: URL, token: Token?) -> AnyPublisher<Data, Error>
}

Using a protocol for this will help me swap out URLSession for a mock object that allows me to easily experiment with different responses. Note that I'm using Data as an output for my publisher here. This means that callers for publisher(for:token:) wouldn't have access to the response object that's returned by a data task publisher. That's not a problem for me in this case but if it is for you, make sure that you adjust the output (and adapt the code from this post) accordingly.

Here's what my mock networking object looks like:

class MockNetworkSession: NetworkSession {
  func publisher(for url: URL, token: Token? = nil) -> AnyPublisher<Data, Error> {
    let statusCode: Int
    let data: Data

    if url.absoluteString == "https://donnys-app.com/token/refresh" {
      print("fake token refresh")
      data = """
      {
        "isValid": true
      }
      """.data(using: .utf8)!
      statusCode = 200
    } else {
      if let token = token, token.isValid {
        print("success response")
        data = """
        {
          "message": "success!"
        }
        """.data(using: .utf8)!
        statusCode = 200
      } else {
        print("not authenticated response")
        data = """
        {
          "errors": ["invalid_token"]
        }
        """.data(using: .utf8)!
        statusCode = 401
      }
    }

    let response = HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: nil, headerFields: nil)!

    // Use Deferred future to fake a network call
    return Deferred {
      Future { promise in
        DispatchQueue.global().asyncAfter(deadline: .now() + 1, execute: {
          promise(.success((data: data, response: response)))
        })
      }
    }
    .setFailureType(to: URLError.self)
    .tryMap({ result in
      guard let httpResponse = result.response as? HTTPURLResponse,
            httpResponse.statusCode == 200 else {

        let error = try JSONDecoder().decode(ServiceError.self, from: result.data)
        throw error
      }

      return result.data
    })
    .eraseToAnyPublisher()
  }
}

While there's a bunch of code here, it's really not that complex. The first couple of lines only check which endpoint we're calling and whether we received a valid token. Depending on these variables I either return a refreshed token, a success response or an error response. In really you would of course make a call to your server rather write some code like I did here. I use Combine's Future to publish my prepared response with a delay. I also do some processing on this response like I would in a real implementation to check which http status code I ended up with. If I get a non-200 status code I decode the body data into a ServiceError and fail the publisher by throwing an error that we can catch later when we call publisher(for:token:). If I got a 200 status code I return a Data object.

While this code might look a bit silly in the context of my mock, let's take a look at how you can extend URLSession to make it conform to NetworkSession:

extension URLSession: NetworkSession {
  func publisher(for url: URL, token: Token?) -> AnyPublisher<Data, Error> {
    var request = URLRequest(url: url)
    if let token = token {
      request.setValue("Bearer <access token>", forHTTPHeaderField: "Authentication")
    }

    return dataTaskPublisher(for: request)
      .tryMap({ result in
        guard let httpResponse = result.response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {

          let error = try JSONDecoder().decode(ServiceError.self, from: result.data)
          throw error
        }

        return result.data
      })
      .eraseToAnyPublisher()
  }
}

This extension looks a lot more reasonable. It's not quite as useful as I'd like because you'll probably want to have a version of publisher(for:) that takes a URLRequest that you configure in your networking layer. But again, my point isn't to teach you how to abstract your networking layer perfectly. It's to show you how you can implement a token refresh flow in Combine that can deal with concurrent requests. The abstraction I've written here is perfect to provide some scaffolding for this.

The final piece in this puzzle (for now) is a networking object that makes an authenticated request:

struct NetworkManager {
  private let session: NetworkSession

  init(session: NetworkSession = URLSession.shared) {
    self.session = session
  }

  func performAuthenticatedRequest() -> AnyPublisher<Response, Error> {
    let url = URL(string: "https://donnys-app.com/authenticated/resource")!

    return session.publisher(for: url: token: nil)
  }
}

This code doesn't quite meet the mark, and that's fine. We'll fix it in the next section.

When we call performAuthenticatedRequest we want to obtain a token from somewhere and then pass this token to publisher(for:token:) if it turns out that this token is invalid we want to try and refresh it exactly once. If we obtain a token and still aren't authenticated it's not very likely that refreshing the token again will fix this. It's probably a better idea to ask the user to login again or head down some other recovery path that's appropriate for your use case. The key component here is that we build an object that can provide tokens to objects that require them, obtain new tokens as needed, and most importantly does this gracefully without duplicate refresh requests. Let's see how.

Building an authenticator

Authenticator, Token provider, authentication manager, name it what you will. I will call it an authenticator since it handles the user's authentication status. You can call it anything you want, it doesn't matter much. Just name it something good.

The idea of an authenticator is that when asked for a valid token it can go down three routes:

  1. A valid token exists and should be returned
  2. We don't have a token so the user should log in
  3. A token refresh is in progress so the result should be shared
  4. No token refresh is in progress so we should start one

Each of the four scenarios above should produce a publisher, and this should all happen in a single method that returns a publisher that emits a token.

Before I show you my implementation for this method, I want to show you the skeleton for my authenticator:

class Authenticator {
  private let session: NetworkSession
  private var currentToken: Token? = Token(isValid: false)
  private let queue = DispatchQueue(label: "Autenticator.\(UUID().uuidString)")

  // this publisher is shared amongst all calls that request a token refresh
  private var refreshPublisher: AnyPublisher<Token, Error>?

  init(session: NetworkSession = URLSession.shared) {
    self.session = session
  }

  func validToken(forceRefresh: Bool = false) -> AnyPublisher<Token, Error> {
    // magic...
  }
}

Since we'll need to make a network call if the token requires refreshing the authenticator depdends on a NetworkSession. It will also keep track of the current token. In this case I use an invalid token as the default. In an app you'll probably want to grab a current token from the user's keychain and use nil as a default token so you can show a log in screen if no token exists.

The authenticator will need to deal with concurrency gracefully and the refreshPublisher property will be used to determine if a refresh is in progress. Since multiple queues could access refreshPublisher at the same time we want to make sure that only one thread can read refreshPublisher at the same time. This is what the queue property is used for. When I kick off a request I assign a value to refreshPublisher and when the request completes I will set this property to nil again.

Learn more about concurrency and synchronizing access in my post on DispatchQueue.sync and DispatchQueue.async.

Note that my validToken method take a forceRefresh argument. This argument is used to tell the authenticator that it should refresh a token even if it might look like a token should be valid. We'll pass true for this argument in case we get a token error from the server back in the NetworkManager. You'll see why in a moment.

Let's look at the implementation of validToken(forceRefresh:):

func validToken(forceRefresh: Bool = false) -> AnyPublisher<Token, Error> {
  return queue.sync { [weak self] in
    // scenario 1: we're already loading a new token
    if let publisher = self?.refreshPublisher {
      return publisher
    }

    // scenario 2: we don't have a token at all, the user should probably log in
    guard let token = self?.currentToken else {
      return Fail(error: AuthenticationError.loginRequired)
        .eraseToAnyPublisher()
    }

    // scenario 3: we already have a valid token and don't want to force a refresh
    if token.isValid, !forceRefresh {
      return Just(token)
        .setFailureType(to: Error.self)
        .eraseToAnyPublisher()
    }

    // scenario 4: we need a new token
    let endpoint = URL(string: "https://donnys-app.com/token/refresh")!
    let publisher = session.publisher(for: endpoint, token: nil)
      .share()
      .decode(type: Token.self, decoder: JSONDecoder())
      .handleEvents(receiveOutput: { token in
        self?.currentToken = token
      }, receiveCompletion: { _ in
        self?.queue.sync {
          self?.refreshPublisher = nil
        }
      })
      .eraseToAnyPublisher()

    self?.refreshPublisher = publisher
    return publisher
  }
}

The entire body for validToken(forceRefresh:) is executed sync on my queue to ensure that I don't have any data races for refreshPublisher. The initial scenario is simple. If we have a refreshPublisher, a request is already in progress and we should return the publisher that we stored earlier. The second scenario occurs if a user hasn't logged in at all or their token went missing. In this case I fail my publisher with an error I defined to tell subscribers that the user should log in. For posterity, here's what that error looks like:

enum AuthenticationError: Error {
  case loginRequired
}

If we have a token that's valid and we're not forcing a refresh, then I use a Just publisher to return a publisher that will emit the existing token immediately. No refresh needed.

Lastly, if we don't have a token that's valid I kick off a refresh request. Note that I use the share() operator how to make sure that any subscribers for my refresh request share the output from my initial request. Normally if you subscribe to the same data task publisher more than once it will kick off a network call for each subscriber. The share() operator makes sure that all subscribers receive the same output without triggering a new request.

I don't subscribe to the output of my refresh request, that's up to the caller of validToken(forceRefresh:). Instead, I use handleEvents to hook into the receiveOutput and receiveCompletion events. When my request produces a token, I cache it for future use. In your app you'll probably want to store the obtained token in the user's keychain. When the refresh request completes (either succesfully or with an error) I set the refreshPublisher to nil. Note that I wrap this in self?.queue.sync again to avoid data races.

Now that you have an authenticator, let's see how it can be used in the NetworkManager from the previous section.

Using the authenticator in your networking code

Since the authenticator should act as a dependncy of the network manager we'll need to make some changes to its init code:

private let session: NetworkSession
private let authenticator: Authenticator

init(session: NetworkSession = URLSession.shared) {
  self.session = session
  self.authenticator = Authenticator(session: session)
}

The same Authenticator can now be used in every network call you make through a single instance of NetworkManager. All that's left to do is use this authenticator in every network call that NetworkManager can perform.

In this case that's only a single method but for you it could be many, many more methods. Make sure that they all use the same instance of Authenticator.

Let's see what my finished example of performAuthenticatedRequest looks like:

func performAuthenticatedRequest() -> AnyPublisher<Response, Error> {
  let url = URL(string: "https://donnys-app.com/authenticated/resource")!

  return authenticator.validToken()
    .flatMap({ token in
      // we can now use this token to authenticate the request
      session.publisher(for: url, token: token)
    })
    .tryCatch({ error -> AnyPublisher<Data, Error> in
      guard let serviceError = error as? ServiceError,
            serviceError.errors.contains(ServiceErrorMessage.invalidToken) else {
        throw error
      }

      return authenticator.validToken(forceRefresh: true)
        .flatMap({ token in
          // we can now use this new token to authenticate the second attempt at making this request
          session.publisher(for: url, token: token)
        })
        .eraseToAnyPublisher()
    })
    .decode(type: Response.self, decoder: JSONDecoder())
    .eraseToAnyPublisher()
}

Before making my network call I call authenticator.validToken(). This will produce a publisher that emits a valid token. If we already have a valid token then the valid token will be published immediately. If we have a token that appears to be expired, validToken() will fire off a refresh immediately and we'll receive a refreshed token eventually. This means that the token that's passed to the flatMap which comes after validToken() should always be valid unless something strange happened and the validity of our token isn't what it looked like initially.

By using flatMap on validToken() you can grab the token and use it to create a new publisher. In this case that should be your network call.

After my flatMap I use tryCatch. Since the publisher(for:token:) implementation is expected to throw an error and fail the publisher if we receive a non-200 http status code we'll want to handle this in the tryCatch.

I check whether the error I received in my tryCatch is indeed a ServiceError and that its errors array contains ServiceErrorMessage.invalidToken. If I receive something else this could mean that the authenticator noticed that we don't have a token and it failed with a loginRequired error. It could also mean that something else went wrong. We want all these errors to be forwarded to the caller of performAuthenticatedRequest(). But if we received an error due to an expired token, we'll want to attempt one refresh to be sure we can't recover.

Note that I call validToken and pass forceRefresh: true at this point. The reason for this is that I already called validToken before and didn't force a refresh. The token that the authenticator holds appears to be valid but for some reason it's not. We'll want to tell the autenticator to refresh the token even if the token looks valid.

On the next line I flatMap over the output of validToken(forceRefresh:) just like I did before to return a network call.

Either the flapMap or the tryCatch will produce a publisher that emits Data. I can call decode on this publisher to obtain an instance of Response.

The whole chain is erased to AnyPublisher so my return type for performAuthenticatedRequest() is AnyPublisher<Response, Error>.

It takes some setup, and it's definitely not something you'll wrap your head around easily but this approach makes a ton of sense ones you've let it sink in. Especially because we begin our initial request with an access token that should be valid and has already been refreshed if the token appears to be expired locally. A single refresh will be attempted if the initial token turns out to be invalid in case the device clock is off, or a token was marked as expired on the server for security reasons or other reasons.

If the token was refreshed succesfully but we still can't perform our request it's likely that something else is off and it's highly unlikely that refreshing again will alleviate the issue.

In Summary

In this week's post you saw an approach that uses Combine and DispatchQueue.sync to build a token refresh flow that can handle multiple incoming requests at the same time without firing off new requests when a token refresh is already in progress. The implementation I've shown you will pro-actively refresh the user's token if it's already known that the token is expired. The implementation also features a forced refresh mechanism so you can trigger a token refresh at will, even if the locally cached token appears to be valid.

Flows like these are often built on top of arbitrary requirements and not every service will work will with the same approach. For that reason I tried to focus on the authenticator itself and the mechanisms that I use to synchronize access and share a publisher rather than showing you how you can design a perfect networking layer that integrates nicely with my Authenticator. I did show you a basic setup that can be thought of as a nice starting point.

If you have any questions or feedback about this article please let me know on Twitter.

Building a simple remote configuration loader for your apps

Remote configuration is a common practice in almost every app I have worked on. Sometimes these configurations can be large and the implications of a configuration change can be far-reaching while other times a configuration is used to change the number of items shown in a list, or to enable or disable certain features. You can even use remote configuration to set up your networking layer. For example by setting certain headers on a request, providing endpoints for your remote data, and more.

In this week's post I will not go into detail about every possible use case that you might have for a remote configuration. Instead, I will show you how you can create a remote configuration, host it, load it, and cache it for subsequent launches.

While it might be tempting to look at a third-party solution for a feature like this, I want to show you how you can set up a remote configuration yourself because it's much more straightforward than you might think.

Hosting a remote configuration file

There are countless ways to create and host a configuration file. For example, you can generate a configuration file using a Content Management System (CMS), write a JSON file by hand, or generate JSON files by concatenating several JSON files into a single, larger JSON file. In this post we'll keep it simple and write a JSON file by hand.

In terms of hosting your remote configuration file, you could upload the file to a server that you own, use a static file server like Amazon's S3, or use any other platform that allows you to serve JSON files. As you can imagine there are tons of solutions that allow you to serve a static file but I'd like to keep things simple and straightforward.

My personal choice for hosting static files is Amazon's S3. I already have an account there, and creating S3 buckets is fairly simple. And most importantly, it's cheap. Amazon has a generous free tier and even when your data traffic exceeds the free tier, S3 is still very affordable.

You can create an S3 bucket by signing up for an AWS account at https://aws.amazon.com. Just to be clear, I am not affiliated with Amazon and will not receive any money if you create an account through me. They are not paying me to promote their services in any way.

After you've signed up for an AWS account go to the S3 page and click the big Create bucket button. Choose an appropriate name for your bucket (for example com.yourname.app-config) and choose a region. The region you pick is the physical location where your bucket is stored. This means that if you pick EU (Frankfurt) your app config will be served from a server in Frankfurt. It's typically a good idea to choose a region that you would consider to be close to the majority of your users. The closer the server is to your users, the less latency the user will experience while loading your app config. However, since we'll be caching the app config and including an initial config in the app bundle later you don't have to worry about this too much. Your user won't notice when the config loaded slightly slower than you'd like.

After choosing a name and region, click Next. On this screen, you can uncheck the Block all public access option and accept the warning that pops up. The whole purpose of this bucket is to be public so you can serve your config from it. Click Next again and then click Create Bucket. That's it, you've created your first Amazon S3 bucket!

To test your bucket, create a file called config.json on your Desktop (or in any other location you find convenient) and add the following contents to it:

{
  "minVersion": "1.0.0"
}

Select your bucket in the overview on your S3 page and click the Upload button to add a file to your bucket. Select your config.json file and set the Managed public permissions field to Grant public read access to this object(s):

Screenshot of the correct settings for the uploaded config file

After doing this, click the Upload button.

Your file will upload and be visible in your bucket. Click the file name to inspect its details. The last field in the detail view is called Object URL and it contains your config's URL. For example my config was uploaded to https://s3.eu-central-1.amazonaws.com/com.donnywals.blog/config.json.

And that's it! You've uploaded your first config to an S3 bucket. Now let's see how you can use this config in an app.

Loading a remote config in your application

Once you have uploaded your app configuration somewhere, you can start thinking about adding it to your app. There are a couple of important requirements that I think are essential to a good app config setup:

  • The app must be able to function offline or if config loading fails
  • It should be possible to mock the app config for testing purposes
  • Config changes should be applied as soon as possible

The first two requirements are essential in my opinion. If your app doesn't work without loading a configuration first your users will likely experience frustratingly slow startup times that get worse as their network quality degrades. Worse, they wouldn't be able to use your app while they are offline.

The third requirement is a somewhat optional requirement that might not be realistic for every configuration property that you use. For example, if you have config flags for your UI you might not want to immediately redraw your UI when you've loaded a new config. You can probably use your older configuration until the next time the user launches your app. Other features like an update prompt are much easier to implement in a way that's dynamic and applied as soon as your configuration is loaded. In this post I'll show you how you can use Combine to subscribe to changes in your configuration.

First, let's see how you can add a default configuration to your app, load a new configuration, and store this new configuration for later use.

We'll write a ConfigProvider class that takes care of all of this. Since we want to pass this object around without copying it we must use a class for this object. If you'd make this a struct Swift would create a copy every time you pass your config provider to a view model, view, or view controller. This would be fine if the config provider was completely immutable but it's not. When the app initially launches we'll load a cached config, and when this config updates we want to use the updated configuration which means we need to mutate the config provider by making it point to a new config.

Before we write this ConfigProvider, copy the config.json file you created to your project in Xcode and make sure it's included in your app by setting its Target Membership.

Before we write the ConfigProvider, you should also define a struct that your configuration JSON is decoded into. For the sample config you wrote earlier the struct would look like this:

struct AppConfig: Codable {
  let minVersion: String
}

You can name this struct whatever you want, and make sure it matches the JSON that you use in your configuration file.

Let's start building our ConfigProvider by writing a skeleton class that contains the properties and methods that I'd like to use:

class ConfigProvider {

  private(set) var config: AppConfig

  func updateConfig() {
    // here we'll load the config
  }
}

This API is nice and simple. There's a config property that can be used to retrieve the current config, and there's an updateConfig() method that can be used to fetch a remote configuration and update the config property.

The ConfigProvider must be able to load local configuration files and remote configuration files as needed. The provider should always return a configuration file, even if no file was loaded before. That's why you added the config.json to the app bundle earlier.

To keep functionality separated, we'll create two helper objects that are abstracted behind a protocol so they can be swapped out when testing the config provider object. Let's define these protocols first:

protocol LocalConfigLoading {
  func fetch() -> AppConfig
  func persist(_ config: AppConfig)
}

protocol RemoteConfigLoading {
  func fetch() -> AnyPublisher<AppConfig, Error>
}

Both protocols are pretty lean. A LocalConfigLoading object is capable of fetching a local configuration and persisting an AppConfig file to the file system. A RemoteConfigLoading object is capable of loading configuration from a remote server. Before we implement objects that conform to these protocols, we can already write the ConfigProvider's implementation.

Let's look at the initializer first:

class ConfigProvider {

  private(set) var config: AppConfig

  private let localConfigLoader: LocalConfigLoading
  private let remoteConfigLoader: RemoteConfigLoading

  init(localConfigLoader: LocalConfigLoading, remoteConfigLoader: RemoteConfigLoading) {
    self.localConfigLoader = localConfigLoader
    self.remoteConfigLoader = remoteConfigLoader

    config = localConfigLoader.fetch()
  }

  func updateConfig() {

  }
}

This is nice and straightforward. The ConfigProvider takes two objects in its initializer and uses the local loader's fetch method to set an initial local configuration.

The updateConfig method will use the remote provider to fetch a new configuration. To make sure don't have more than one update request in flight at a time, we'll use a DispatchQueue and dispatch to it synchronously.To learn more about this you can read my post on using DispatchQueue.sync and DisptachQueue.async.

Since we need to subscribe to a publisher to obtain the remote config, we also need a property to hold on to a cancellable. The following code should be added to the ConfigProvider:

private var cancellable: AnyCancellable?
private var syncQueue = DispatchQueue(label: "config_queue_\(UUID().uuidString)")

func updateConfig() {
  syncQueue.sync {
    guard self.cancellable == nil else {
      return
    }

    self.cancellable = self.remoteConfigLoader.fetch()
      .sink(receiveCompletion: { completion in
        // clear cancellable so we could start a new load
        self.cancellable = nil
      }, receiveValue: { [weak self] newConfig in
        self?.config = newConfig
        self?.localConfigLoader.persist(newConfig)
      })
  }
}

If we already have a cancellable stored we return early to avoid making two requests. If we don't have a cancellable, the remote loader's fetch() method is called. When this call completes the cancellable is cleaned up. When a new config is received, it is assigned to self?.config to make it available to users of the config provider. I also call persist on the local config loader to make sure the loaded config is available locally.

Let's look at the implementation for the local config loader:

class LocalConfigLoader: LocalConfigLoading {
  private var cachedConfigUrl: URL? {
    guard let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
      return nil
    }

    return documentsUrl.appendingPathComponent("config.json")
  }

  private var cachedConfig: AppConfig? {
    let jsonDecoder = JSONDecoder()

    guard let configUrl = cachedConfigUrl,
          let data = try? Data(contentsOf: configUrl),
          let config = try? jsonDecoder.decode(AppConfig.self, from: data) else {
      return nil
    }

    return config
  }

  private var defaultConfig: AppConfig {
    let jsonDecoder = JSONDecoder()

    guard let url = Bundle.main.url(forResource: "config", withExtension: "json"),
          let data = try? Data(contentsOf: url),
          let config = try? jsonDecoder.decode(AppConfig.self, from: data) else {
      fatalError("Bundle must include default config. Check and correct this mistake.")
    }

    return config
  }

  func fetch() -> AppConfig {
    if let cachedConfig = self.cachedConfig {
      return cachedConfig
    } else {
      let config = self.defaultConfig
      persist(config)
      return config
    }
  }

  func persist(_ config: AppConfig) {
    guard let configUrl = cachedConfigUrl else {
      // should never happen, you might want to handle this
      return
    }

    do {
      let encoder = JSONEncoder()
      let data = try encoder.encode(config)
      try data.write(to: configUrl)
    } catch {
      // you could forward this error somewhere
      print(error)
    }
  }
}

The most interesting parts are the fetch() and persist(_:) methods. In fetch() I first access self.cachedConfig. This property returns an optional AppConfig. It checks whether a config is stored in the app documents directory and decoding it. If no file exists, or the decoding fails this property is nil which means that we should use the default config that was bundled with the app. This config is loaded by the defaultConfig property and can't fail. If it does, the project is misconfigured.

After grabbing the default config it is passed to persist(_:) so it's copied to the documents directory which means it'll be loaded from there on the next launch.

The persist method encodes the AppConfig that it receives and writes it to the documents directory. It's a whole bunch of code, but the principles this is built on are fairly straightforward.

Let's look at the remote config loader next:

class RemoteConfigLoader: RemoteConfigLoading {
  func fetch() -> AnyPublisher<AppConfig, Error> {
    let configUrl = URL(string: "https://s3.eu-central-1.amazonaws.com/com.donnywals.blog/config.json")!

    return URLSession.shared.dataTaskPublisher(for: configUrl)
      .map(\.data)
      .decode(type: AppConfig.self, decoder: JSONDecoder())
      .eraseToAnyPublisher()
  }
}

This class is nice and tiny. It uses Combine to load my configuration file from the S3 bucket that I created earlier. I extract data from the output of my data task, I decode it into AppConfig and then I erase to AnyPublisher to keep my return type nice and clean.

Let's make one last change. When a new config is loaded we want to be able to respond to this. The easiest way to do this is by making ConfigProvider conform to ObservableObject and marking config as @Published:

class ConfigProvider: ObservableObject {

  @Published private(set) var config: AppConfig

  // rest of the code...
}

When using this config loader in a SwiftUI app you could write something like the following in your App struct:

@main
struct ConfigExampleApp: App {
  let configProvider = ConfigProvider(localConfigLoader: LocalConfigLoader(),
                                      remoteConfigLoader: RemoteConfigLoader())

  var body: some Scene {
    WindowGroup {
      ContentView()
        .environmentObject(configProvider)
        .onAppear(perform: {
          self.configProvider.updateConfig()
        })
    }
  }
}

This code makes the config provider available in the ContentView's environment. It also updates the config when the content view's onAppear is called. You can use the config provider in a SwiftUI view like this:

struct ContentView: View {
  @EnvironmentObject var configProvider: ConfigProvider

  var body: some View {
    Text(configProvider.config.minVersion)
      .padding()
  }
}

When the config updates, your view will automatically rerender. Pretty neat, right?

In a UIKit app you would add a property to your AppDelegate and inject the config provider into your view controller. The code would look a bit like this:

class AppDelegate: NSObject, UIApplicationDelegate {
  let configProvider = ConfigProvider(localConfigLoader: LocalConfigLoader(),
                                      remoteConfigLoader: RemoteConfigLoader())

  var window: UIWindow?

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

    configProvider.updateConfig()

    let window = UIWindow()
    window.rootViewController = ViewController(configProvider: configProvider)
    window.makeKeyAndVisible()

    self.window = window

    return true
  }
}

To receive configuration changes you can subscribe to the provider's $config property as follows:

configProvider.$config.sink { newConfig in
  // use the new config
}
.store(in: &cancellables)

Of course the exact usage will vary per app, but I'm sure this should help you to get started. The main point is that you know how you can load a remote config and cache it locally for future usage.

In Summary

In this week's post you have seen several interesting techniques. You learned how you can upload a configuration file for your app to an S3 bucket. You saw how you can load this file and cache it locally for future use. The contents of the config I've shown you are very basic but you can add tons of information to your config file. Some ideas are a minimum version that your users should have installed or feature flags to enable or disable app features remotely.

I've also shown you how you can make your config provider observable so you can react to changes in both SwiftUI and UIKit. This allows you to present popovers or show / hide UI elements as needed by reading values from the config object.

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

Formatting dates in the user’s locale using DateFormatter in Swift

Working with dates is hard, there is no doubt about that. And formatting dates properly for every user of your app is no easier (if you want to do everything manually). Luckily, the system can help us.

For example, in the US one would write "October 15" while in The Netherlands we write 15 oktober.

Note that the order of the date and the month is different, the spelling of the month is different and the capitalization is different too.

The DateFormatter in iOS will handle a lot of this for you. For example, if you'd use the following code on a device that uses nl as its locale you would see the output that I added to this snippet as a comment:

let now = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd MMMM"
formatter.string(from: now) // 15 oktober

The output of this code is spot on. Exactly what we need and it matches the specified date format perfectly. If you'd run the same code on a device that uses en_us as its locale the output would be 15 October.

The date formatter got the spelling and capitalization right but the date and month are in the wrong order.

You can fix this by using setLocalizedDateFormatFromTemplate on your date formatter instead of assigning its dateFormat directly. Let's look at an example that runs on a device with nl as its locale again:

let now = Date()
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("dd MMMM")
formatter.string(from: now) // 15 oktober

That still works, perfect. If you'd run this code on an en_us device the output would be October 15. Exactly what we need.

If you want to play around with setLocalizedDateFormatFromTemplate in a Playground you can give it a go with the following code that uses a date formatter in different locales:

import Foundation

let now = Date()
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_us")
formatter.setLocalizedDateFormatFromTemplate("dd MMMM")
formatter.string(from: now) // October 15

formatter.locale = Locale(identifier: "nl")
formatter.setLocalizedDateFormatFromTemplate("dd MMMM")
formatter.string(from: now) // 15 oktober

If you have questions about this Quick Tip, or if you want to reach out to me for other reasons then don't hesitate to send me a message on Twitter.

It's a good thing that Swift and Foundation can handle these kinds of

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.