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.

Configuring error types when using flatMap in Combine

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

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

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

Understanding how flatMap works with Error types

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

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

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

  })

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

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

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

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

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

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

  })

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Let's look at an example:

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

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

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

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

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

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

In Summary

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

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

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

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

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

3 tips to work through a technical coding interview

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

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

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

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

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

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

1. Study

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

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

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

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

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

2. Think out loud

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

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

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

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

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

3. Ask questions

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

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

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

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

(bonus) 4. Be honest

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

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

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

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

In Summary

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

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

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

Dispatching async or sync? The differences explained

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

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

DispatchQueue.main.async {
  // do something
}

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

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

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

Understanding DispatchQueue.async

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

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

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

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

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

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

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

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

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

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

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

Understanding DispatchQueue.sync

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

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

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

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

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

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

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

A very simple example would be this DateFormatterCache object:

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

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

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

      return formatter
    }
  }
}

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

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

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

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

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

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

In Summary

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

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

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