WWDC Notes: Protect mutable state with Swift actors

Data races make concurrency hard. They occur when two threads access the same data and at least one of them is a write. It’s trivial to write a data race, but it’s really hard to debug.

Data races aren’t always clear, aren’t always reproducible, and might not always manifest in the same way.

Shared mutable state is needed for a data race to occur. Value types don’t suffer from data races due to the way they work; they’re copied.

When you pass an array around, copies are created. This is due to array’s value semantics.

Even an object that’s a value type can be captured in a racy way. When you mutate a value type in a concurrent code block, Swift will show a compiler error since you’re about to write a data race.

Shared mutable state requires synchronization. Existing methods are:

  • Atomics
  • Locks
  • Serial dispatch queues

All three require the developer to carefully use these tools.

Actors

Actors are introduced to eliminate this problem. Actors isolate their state from the rest of the program. This means that all access to state has. To go through the actor. An actor ensures mutually-exclusive access to state.

What’s nice is that you cannot forget to synchronize an actor.

Actors can do similar things to structs, enums, and classes. They are reference types and their unique characteristic is in how they synchronize and isolate data.

actor Counter {
  var value = 0
  func increment() -> Int {
    value = value + 1
    return value
  }
}

In this code, the actor will ensure that value is never read / mutated concurrently.

let counter = Counter()

asyncDetached {
  print(await counter.increment())
}

asyncDetached {
  print(await counter.increment())
}

This code does not cause a data race even though we have no idea how/when these detached tasks run. They might run after each other or at the same time. The actor will ensure we don’t have a data race on value.

Interacting with actors is done asynchronously. The actor might have your calling code wait for a while to free its resources and avoid data races.

Extensions on an actor can access an actor’s state because it’s considered internal to the actor. You can access an actor’s state synchronously from within the actor. In other words, within an actor, your code is uninterrupted and you don’t need to worry about suspensions.

Actor reentrancy

There is an important exception to this though. When a function on an actor runs it’s not interrupted, unless you have an wait within the function. If you await in an actor function, the function is suspended.

Consider this code:

actor ImageDownloader {
  private var cache: [URL: Image] = [:]

  func image(from url: URL) async throws -> Image? {
    if let cached = cache[url] {
      return cached
    }

    let image = try await download(from: url)

    cache[url] = image
    return image
  }
}

If you call this code, no data races for cache can occur. But if you call this method twice for the same url, here’s what happens:

  • CALL1 sees that no image is cached for url
  • CALL1 starts download,
  • CALL1 suspends
  • CALL2 sees that no image is cached for url
  • CALL2 starts download,
  • CALL2 suspends
  • CALL1’s call to download completes and CALL1 resumes
  • CALL1 caches image
  • CALL2’s call to download completes and CALL2 resumes
  • CALL2 overrides image with new version of image for same URL

While an actor’s function is suspended, the underlying data can be changed by another call to that function.

One workaround here would be to not overwrite the image when it’s re-downloaded:

cache[url] = cache[url, default: image]

Video mentions code associated with the video for a better solution; not found.

Ideally, all mutations in an actor are synchronous. You should expect that state is changed after you’ve used an await in your actor code. Any assumptions should be checked after an await.

Actor isolation

Protocol conformances must respect actor isolation.

For example

extension SomeActor: Hashable {
  func hash(into hasher: inout Hasher) {
    hasher.combine(someProperty)
  }
}

This will throw a compiler error. hash(into:) is an instance method on SomeActor which means that it must be called with async since the actor might run the function call at a later time if the actor is already busy.

However, Hashable requires hash(into:) to be synchronous. This is a problem because for an actor to guarantee isolation, we can’t call it synchronously.

To work around this, we can use nonisolated:

extension SomeActor: Hashable {
  nonisolated func hash(into hasher: inout Hasher) {
    hasher.combine(someProperty)
  }
}

This will make it so that the function is not isolated anymore. As long as someProperty is immutable, this will work. We know that someProperty can’t be mutated so reading it without isolation shouldn’t be a problem. After all, a data race is only a data race if one of the accesses mutates state.

If someProperty is mutable though, we’d still get a compiler error because we don’t know who else might access someProperty (and potentially mutate it).

Closures can be isolated to the actor.

extension SomeActor {
  func doSomething() {
    self.someList.map { item in 
      return self.infoFor(item)
    }
  }
}

infoFor is defined on SomeActor but we don’t need to await self.infoFor because we know this closure never leaves the scope of the actor. Map’s closure isn’t escaping so we’re always actor isolated.

extension SomeActor {
  func runLater() {
    asyncDetached {
      await doSomething()
    }
  }
}

In this example, the closure isn’t run immediately within the scope of the actor since it’s detached. This means we must await doSomething() since we’re no longer actor isolated.

If an actor owns a reference type that might be exposed to the outside of an actor, we have a potential for a data race. This isn’t a problem for value types. However, it is a problem for classes since the actor will hold a reference to an instance of a reference type. If this references is (safely) passed outside of the scope of the actor, this non-actor scope might cause a data race on that reference type instance.

Objects that can be safely shared concurrently are called Sendable types.

If you copy a something from one place to the other and each can modify it without issue, that object is sendable.

  • Value types are sendable because they’re copied
  • Actors are sendable because they isolate state
  • Immutable classes can be sendable. These are classes with only let properties
  • Internally synchronized classes can be sendable
  • Functions aren’t always sendable. They are if they are @Sendable

Sendable describes a common but not universal property of type.

Swift will enforce Sendable at compile time so you’ll know when you’re at risk of leaking non-Sendable information.

Sendable is a protocol that you can have classes conform to. Swift will automatically check correctness for you from that point. Something is Sendable if all of its properties are Sendable, similar to Decodable’s synthesized properties.

Sendable objects with generics must have the generic be Sendanle too. You can add conditional conformance to objects based on generics being Sendable for example.

Making objects Sendable if you need them in a concurrent situation is a great way to have Swift help you out when you might have a data race.

@Sendable function types conform to Sendable

Sendable closures cannot have mutable captures, must capture Sendable types only, and can’t be both sync and actor isolated.

asyncDetached takes a sendable closure.

var c = Counter()
asyncDetached {
  c.increment()
}

This doesn’t work because c is not Sendable

Main actor

The main thread in an app is where all kinds of UI operations are performed. Whenever you interact with the UI you’re on the main thread.

Slow tasks should be performed away from the main thread so it doesn’t freeze.

Main thread is good for quick operations.

DispatchQueue.main.async is used to dispatch onto the main queue. This is very similar to running on an actor. If you’re on the main thread you can access it safely. Just like you can safely access an actor’s state from within the actor. If you want to run something on the actor from the outside you do so async with await. This is similar to running code on the main queue async with DispatchQueue.main.async.

The Main Actor in Swift is an actor that represents the main thread.

Main actors always use the main dispatch queue as their underlying queue. It’s interchangeable with DispatchQueue.main from the pov of the runtime.

If you mark something with @MainActor, it always runs on the main actor:

@MainActor func doSomething() {}

This code must be called async from outside of the main actor:

await doSomething()

Whole types cal be placed on the main actor with @MainActor:

@MainActor class SomeClass

Opt out of running on the main actor in such a class with nonisolated.

WWDC Notes: Explore structured concurrency in Swift

Structured programming uses a static scope. This makes it very easy to reason about code and its flow. Essentially making it trivial to understand what your code does by reading it from top to bottom.

Asynchronous and concurrent code do not follow this structured way of programming; it can’t be read from top to bottom.

Asynchronous functions don’t return values because the values aren’t ready at the end of the function scope. This means that the function will communicate results back through a closure at a later time.

It also means that we don’t use structured programming for error handling (no throws for example).

We need nesting if we want to use the produced results for another asynchronous operation.

An async function doesn’t take a completion handler and is instead marked with async, and it returns a value.

By using await to call async code we don’t have to nest in order to use results from an async function, and we can throw errors instead of passing them to a completion handler.

This brings us much closer to structured programming.

Tasks are a new feature in Swift and they are new in Swift. A task provides an execution context in which we can write asynchronous code. Each task runs concurrently with other tasks. They will run in parallel when appropriate.

Due to deep integration the compiler can help prevent bugs.

Async let task

An async let task is the easiest kind of task.

When writing let thing = something(), something() is evaluated first and its result is assigned to let thing.

If something() is async, you need it to run first and then assign to let thing. You can do this by marking let thing as async:

async let thing = something()

When evaluating this, a child task is created. At the same time, let thing is assigned a placeholder. The parent task continues running until at some point we want to use thing. We need to mark this point with await:

async let thing = something()

// some stuff

makeUseOf(await thing)

At this point, the parent context will suspend and await the completion of the child task which will fulfill the placeholder. If the async function can throw, you must prefix with try await instead.

When calling multiple async functions in one scope, you can write this:

func performAsyncJob() async throws -> Output {
  let (data, _) = try await fetchData()
  let (meta, _) = try await meta()

  return Output(data, meta)
}

This will first run (and await the output of) fetchData, and meta is run after.

After meta is done, we return Output.

If the two await lines don’t depend on each other, we can run then concurrently by using async let:

func performAsyncJob() async throws -> Output {
  async let (data, _) = fetchData()
  async let (meta, _) = meta()

  return Output(try await data, try await meta)
}

This will not suspend the parent task until the await is encountered, and both tasks will be running concurrently.

A parent task can spawn one or more child tasks. A parent task can only complete its work if its child tasks have completed their work. If one of the child tasks throws an error, the parent task should immediately exit. If there are multiple child tasks running, the parent will mark any in-flight tasks as cancelled before exiting. Marking a task as cancelled does not stop the task; it’ll just tell the task that its output is no longer needed; the task must handle its cancellation. If a task has any child tasks when cancelled, its child tasks will be automatically marked as cancelled too.

A parent task will only finish when all of its child tasks are marked as finished,

This guarantee of always finishing tasks (either successfully, through cancellation, or by throwing an error) is fundamental to concurrency in Swift.

Cancellation in Swift tasks is cooperative. The task is not stopped when cancelled. A task must check its own cancellation status at reasonable times, whether it’s actually async or not. This means you should design your tasks with cancellation in mind; especially if they are long-running. You should always aim to stop execution as soon as possible when a task is cancelled.

You can do this with try Task.checkCancellation(). This will check the cancellation status for the current task and throws an error if/when it is. If it’s more appropriate you can also use Task.isCancelled. When your task is cancelled you can throw an error which is what Task.checkCancellation() does. But you can also return an empty result, or a partial result. Make sure you document this explicitly so callers of your function know what to expect.

Group task

In the talk a structure like this is shown:

func fetchSeveralThings(for ids: [String]) async throws -> [String: Output] {
  var output = [String: Output]()
  for id in ids {
    output[id] = try await performAsyncJob()
  }
  return output
}

func performAsyncJob() async throws -> Output {
  async let (data, _) = fetchData()
  async let (meta, _) = meta()

  return Output(try await data, try await meta)
}

For every id, one task with two child tasks is spawned. await performAsyncJob is the parent, and fetchData and meta create the child tasks. In the for loop, we only have one active task at a time since we await performAsyncJob in the loop. This means that Swift can make certain guarantees about our concurrency. It knows exactly how many tasks are active at a time.

We can use a task group to have multiple calls to performAsyncJob active. Tasks that are created in a group cannot escape the scope of their group.

You create a task group through the withThrowingTaskGroup(of: Type.self) function. This function takes a closure that receives a group object. You add new tasks to the group by calling using on the group:

func fetchSeveralThings(for ids: [String]) async throws -> [String: Output] {
  var output = [String: Output]()
  try await withThrowingTaskGroup(of: Void.self) { group in 
    for id in ids {
      group.async {
        output[id] = try await performAsyncJob()
      }
    }
  }

  return output
}

Child tasks start immediately when they’re added to the group.

By the end of the closure, the group goes out of scope and all added child tasks are awaited.

This means that that there’s one task in fetchSeveralThings. This task has a child task for each id in the list. And each child task has several tasks of its own.

The code above would have a compiler error due to a data race on output. It’s being mutated by several concurrently running tasks. Data races are common in concurrent code. Dictionaries are not concurrency-proof; they should only be mutated one by one.

Task creation takes a @Sendable closure and it cannot capture mutable variables. Sendable closures should only capture value types, actors, or classes that implement synchronization.

The Protect mutable state with Swift actors provides more info.

To fix the problem, child tasks can return a value instead of mutating the dictionary directly:

func fetchSeveralThings(for ids: [String]) async throws -> [String: Output] {
  var output = [String: Output]()
  try await withThrowingTaskGroup(of: (String, Output).self) { group in 
    for id in ids {
      group.async {
        return (id, try await performAsyncJob())
      }
    }

    for try await (id, result) in group {
      output[id] = result
    }
  }

  return output
}

This for try await loop runs sequentially so the output dictionary is mutated one step at a time.

Async sequences are covered more in the Meet AsyncSequence session.

While task groups are a form of structured concurrency, the task tree rules work slightly differently.

When one of the child tasks in the group fails with an error (and it throws), this will cancel all other child task just like you’d expect since it’s the same as async let. The main difference is that when the group goes out of scope its tasks aren’t cancelled. You can call cancelAll on the group before exiting the closure that’s used to populate the task.

Unstructured tasks

Structured concurrency has a clear hierarchy and defined rules. Sometimes there are situations where you need unstructured concurrency; without a structured context.

For example, you might need to launch something async from a not yet async context. This means you don’t have a task yet. Other times, tasks might live beyond the confines of a single scope. This would be common when implementing delegate methods with concurrency.

Async tasks

Imagine a collection view delegate method where you want to fetch something async for your cell.

This would not work

// shortened
func cellForRowAt() {
  let ids = getIds(for: item) // item is passed to cellForRowAt
  let content = await getContent(for: ids)
  cell.content = content
}

So we need to launch an unstructured task

// shortened
func willDisplayCellForItem() {
  let ids = getIds(for: item) // item is passed to willDisplayCellForItem
  async {
      let content = await getContent(for: ids)
      cell.content = content
    }
}

The async function runs code asynchronously on the current actor. To make this the main actor you can annotate a class with @MainActor

@MainActor
class CollectionDelegate {
  // code
}
  • An unstructured task will inherit actor isolation and priority of the origin context
  • Lifetime is not confined to a scope
  • Can be launched anywhere
  • Must be manually cancelled or awaited

SIDENOTE all async work is done in a task. Always.

Cancellations and errors do not automatically propagate when using an unstructured task.

We can put tasks in a dictionary to keep track of them:

@MainActor
class CollectionDelegate {
  var tasks = [IndexPath: Task.Handle<Void, Never>]()

  func willDisplayCellForItem() {
    let ids = getIds(for: item) // item is passed to willDisplayCellForItem
    tasks[item] = async {
      defer { tasks[item] = nil }

        let content = await getContent(for: ids)
        cell.content = content
      }
  }
}

Storing the task allows you to cancel it. Should be removed if the task finished. Defer can do this. This way we don’t cancel anything.

Because we run on the main actor (async inherits the actor), we know that we don’t have a data race on tasks. Only one operation will mutate (or read) it at a time.

We can use tasks[item]?.cancel() in didEndDisplay to cancel the task manually.

Detached tasks

Sometimes you want to not inherit any actor information and run a task completely on its own. This can be done with a detached task. They work the same as async tasks but they don’t run in the same context as they’re crated in. You can pass parameters for priority.

Imagine caching the result of getContent in the code. This can be detached from the main actor.

@MainActor
class CollectionDelegate {
  var tasks = [IndexPath: Task.Handle<Void, Never>]()

  func willDisplayCellForItem() {
    let ids = getIds(for: item) // item is passed to willDisplayCellForItem
    tasks[item] = async {
      defer { tasks[item] = nil }

        let content = await getContent(for: ids)

      asyncDetached(priority: .background) {
        writeToCache(content)
      }

        cell.content = content
      }
  }
}

This task will run on its own actor and takes a low priority since it’s not super important to do it as soon as possible.

In a detached task you can create a task group. This would allow you to run a bunch of async work concurrently, and to cancel this work easily by cancelling the detached task since the detached task would be a parent task for all child tasks.

asyncDetached(priority: .background) {
  withTaskGroup(of: Void.self) { group in 
    group.async { writeToCache(content) }
    group.async { ... }
    group.async { ... }
  }
}

Since the detached task is a background task, the priority also applies to all of its child tasks.

Tasks are just part of Swift concurrency. They integrate with the rest of this large feature.

Tasks integrate with the OS and have low overhead. The behind the scene sessions provides more info.

WWDC Notes: Meet async await in Swift

There are tons of async await compatible functions built-in into the SDK. Often with an async version and completion handler based function.

Sync code blocks threads, async code doesn’t

When writing async code with completion handlers you unblock threads but it’s easy to not call your completion handlers. For example when you use a guard let and only return in the else clause. Swift can’t enforce this in the compiler which can lead to subtle bugs.

You can’t throw errors from completion handlers. We usually use Result for this. This adds “ceremony” to our code which isn’t ideal.

Futures can help clean up a callback based flow. But while it’s better and you can’t forget to call completion handlers with that, it’s still not ideal.

Async functions fix this. An async function is suffixed with async:

func doSomething() async

The keyword appears before the return type and before throws:

func doSomething() async throws -> ReturnType

Calling an async function and retrieving its result uses await:

let result = await doSomething()

If the method throws, you use try await:

let result = try await doSomething()

On the line after your async call, the result of doSomething() is available. It was produced without blocking the current thread; execution of your code is paused and the thread is freed up where you write await.

Not having to nest completion closures, call completion handlers, and forward errors makes code much simpler. Errors can be thrown so you don’t need Result.

In addition to functions, properties and initializers can also be async.

var property: Type? {
  get async {
    return await self.computeProperty()
  }
}

If the getter can throw, add throws after the async:

var property: Type? {
  get async throws {
    return await self.computeProperty()
  }
}

We can also use asynchronous sequences. These sequences generate their values asynchronously. We use them like this:

for await value in list {
  let transformed = await transform(value)
  // use transformed
}

There’s a specific session on “Meet AsyncSequence”. There’s also a specific session on Swift’s Structured Concurrency that explains running tasks in parallel for example.

Normal functions calls start, do work, return something. When you call a function, your “source” function gives up control to the called version which will in turn give control back when it’s done. This chain always keeps control of the thread.

In an async function, the function can suspend and allow the system to decide whether it will give control back to the async function by resuming it, or it might let somebody else do work on the thread until it makes sense for the async function to get control back.

Marking something as async or with await doesn’t guarantee that your function will suspend. Just that it might suspend. Or rather, will suspend if needed.

The await keyword signals to the developer that the state of your app can change dramatically while a function is suspended and the thread that the function is on is free to do other work. Swift does not guarantee which thread it will resume your function on. This is an implementation detail that you shouldn’t care about.

Since an async function can suspend, you have to call it from an async context to account for this suspension. Await marks where execution might be suspended. While a function is suspended, other work can be done on the thread (not guaranteed).

XCTest has support for async out of the box. No more need for expectation.

You can test async code by marking the test as async and calling your async work like you would normally, asserting that no errors are thrown for example.

Calling asynchronous code from a SwiftUI view (or non-async context) is done by calling the async task function:

async {
  // call async code
}

This makes it easy to call out into async code from a non-async place.

To learn more:

  • Explore structured concurrency in Swift
  • Discover concurrency in SwiftUI

Getting started is easiest by starting small with some built-in Apple APIs that were converted to be async.

A common pattern to update is one where you have a completion handler. The Swift compiler automatically provides async versions of imported Objective-C code that takes a completion handler.

Some delegate methods take a completion handler that developers must call to communicate an async result. Similar to functions that developers call, these delegate methods are now also async which means we can return values from them or throw errors rather than having to call a completion handler.

There are several sessions about this. The Core Data async/await one is an example of this.

NSAsynchronousFetchRequest fetches objects asynchronously, making it a good candidate to integrate with async/await. This API works with a completion handler.

We can wrap custom async work through continuations. A continuation is used to suspend a function, and resume it when appropriate. We do so through withCheckedThrowingContinuation. If you want a non-throwing version, withThrowingContinuation. You can await a call to these functions to provide a suspension point.

Then you can do whatever you need and call resume(throwing:): or resume(returning:) to resume the code again after having done the work you needed to do.

You must always call resume once. If there are code paths where you don’t call resume, Swift will tell you. Calling resume more than once is a problem and ensures that your code crashes if this happens.

You can store a checked continuation on a class. You can then resume (and nil out) a continuation when needed. This can be used for await a delegate’s response to an action.

Swift concurrency: Behind the scenes explains more about these suspend/resume cycle.

Thoughts on Combine in an async/await world

When Apple announced their own Functional Reactive Programming framework at WWDC 2019 I was super excited. Finally, a simplified, easy to use framework that we could use to dip our toes in FRP.

What made it even better is that SwiftUI makes heavy use of Combine, which means that Apple had to buy in to the technology themselves. This made it seem unlikely that Apple would abandon the technology any time soon.

Then WWDC 2020 came around and there weren’t any meaningful changes to Combine. It was hardly even mentioned in the sessions. To me, this was a signal that Apple was happy with where Combine’s at and it didn’t need a ton of updates which is absolutely fine.

And then the Swift team started implementing Swift’s overhauled concurrency features like async/await, structured concurrency and even asynchronous collections. Once people saw this, I regularly got questions like “Will these features replace Combine or make Combine obsolete?”.

And I honestly didn’t know what async/await would mean for Combine at the time. And I still don’t know for sure, but I have some thoughts.

Combine is great at observing things and receiving changes. This is also how I often explain Functional Reactive Programming. It’s about responding to changes. When a property changes, a user taps something, or a notification is sent through Notification Center, these are things you might want to receive and react to in some way.

This output can be transformed, and eventually displayed to the user, or used to influence something else in your app. The main idea is that you reacted to a specific change in your app’s environment.

As far as I know, async/await won’t be very well suited to observe an @Published property, or to listen for notifications in Notification Center. These will most likely be the places where Combine continues to excel and be a fantastic way to react to changes in your app, altough at the same time Async Streams might change this in the future.

On the other hand, there’s a whole range of uses cases where Combine can be used today that will eventually be obsoleted when iOS 15 is your minimum deployment target since async/await is not currently backwards deployable.

An example of this is networking.

Combine (and RxSwift for that matter) is commonly used in networking layers. We treat our data task as publishers, transform their outputs, and eventually we forward this transformed output to our UI for display in one way or the other.

While this is convenient and works well, I don’t think this was ever the best use case for Combine. Swift’s new concurrency features are far better suited for these kinds of tasks that you want to run. After all, Combine never was intended to be a task runner. It was intended to react to streams of values caused by state changes.

I’m curious to see how this unfolds, and I might change my mind on some of this down the line. After all, I’ve only spent a couple of hours seriously looking at Swift’s new concurrency features.

Combine will most likely stick around for a bit, but its uses will for sure be limited once you decide you’re all in on iOS 15 and async/await. Until then, I would recommend you make the best use of Combine you can, and for the time being I won’t be removing networking examples from my Practical Combine book. I will, however update the book to have some callouts where async/await might be a better choice, and I’ll add more examples of where Combine fits in a world where async/await exists.

The iOS Developer’s guide to WWDC 2024

This post was originally published in 2021 and has been touched up for 2024

WWDC is always an exciting time for iOS engineers. It's the one week a year where we're all newcomers to a whole range of features and APIs that Apple has just unleashed upon the world through their latest Xcode, macOS, iOS, tvOS, iPadOS, visionOS, and watchOS betas. The entire community comes out of hiding and we all come together to share thoughts, experiences, opinions, and findings.

For a whole week, Apple releases dozens of sessions on different topics, and we all scramble to watch them as soon as possible to make sure we're all up to date with whatever the latest and greatest is. As a newcomer, or a person with a regular sleeping schedule, this can be quite intimidating.

So that's why I figured I'd put together a little guide to making the most out of WWDC without being completely overwhelmed and intimidated by the sheer volume of content that Apple is about to unleash on us all.

Let's jump right in, shall we.

Tip 1: Breathe

Once Apple opens up that firehose of new content and beta's it's a scramble. You immediately start up the Beta downloads once they're up. In the meantime, you scan the documentation for new things and you're looking at the Apple Developer app to try and figure out which sessions you're going to watch and when.

It's a lot to take in all at once.

When you blink twice, you'll see the first people Tweet about the cool things they're already doing with the new Xcode beta while you're still waiting for that .xip file to finally finish extracting. The FOMO becomes more real with the second.

Just breathe. Take a moment, and breathe.

The Xcode betas aren't going anywhere. You'll have plenty of time to mess around with new APIs if you want to. Because even though it looks like everybody is exploring all the new things in depth within seconds of the first betas becoming available, a lot of us are much, much slower. In fact, I'd say a majority of us are not on top of everything all the time, and you don't have to be either.

Sit back, breathe, and proceed at a comfortable pace.

You'll find that breathing, and taking it easy are a recurring theme in this guide. That's because it's important that you do.

Tip 2: Figure out what's relevant to you

When you keep an eye on Twitter throughout the week of WWDC you'll find that everybody is talking about the latest and greatest. For WWDC 2021 I'm sure everybody's going to be all over what's new in SwiftUI, Swift 5.5's async/await, and whatever other new and exciting frameworks and APIs Apple releases.

Personally, I love exploring the new things too. It's new, it's fresh, it's exciting. But let's be honest; it's completely irrelevant for most of us in the short term. Apple isn't going to officially release anything they announce until more than three months later, so while it's fun to explore new things, it's not likely that you'll need to be aware of everything.

Whenever I'm going through WWDC content I try to focus on two types of sessions:

  • The super interesting sessions on new technology (for fun)
  • Sessions that cover best practices or advances in technologies I'm already familiar with

That's right, I love watching sessions on stuff that's not new at all. Sometimes these sessions don't even involve any new features and instead cover (usually undocumented) tips, tricks, and best practices that I can incorporate into my work almost immediately. These sessions are most exciting to me because they are most relevant to me in the short term.

So when you're in doubt about which sessions to watch, I would recommend to focus on what's relevant to you at the time rather than a new technology that everybody on the internet seems to be all over.

Tip 3: Move at a comfortable pace

I can't stress it enough; that firehose of content that Apple unleashes during WWDC is intimidating. Apple will upload dozens of videos and each one will look more attractive than the other. You might feel the pressure to watch as many videos during the day as you can. Heck, you might even be so excited that it's excitement that drives you. And my advice to you is that you don't give in to that urge.

Watch videos in a pace that is comfortable to you. If you're going over more than a couple of topics in the timespan of a few hours there's a good chance the contents won't really stick, and you'll be too tired to explore the APIs you've just learned about.

Move at a pace that is comfortable to you. If that means watching two videos a day and spending a few hours exploring in code, then that's exactly what you should do. If you prefer watching more videos and not exploring until later,that's fine too! And if you want to skip the videos and just code all day long, guess what? All good!

The videos aren't going anywhere for a long time. Watch them as needed. Maybe even pick a few videos from last year's WWDC if they're referred to from one of this year's videos. It's your choice.

WWDC is not a race, so focus on having fun with it.

Tip 4: Don't skip the State of the Union

If you've ever attended an in-person WWDC you'll know that the main keynote that starts at 10AM PDT on the Monday of WWDC is the main event. People get in line for a good seat early in the morning; some even start queueing at 11PM the day before. It's where we see a first glimpse of all the goodness we'll get to learn more about in the week to come. However, as a developer this main event is probably not the most interesting first day presentation you can watch.

After the main Keynote event, at 2PM PDT, there's a presentation called State of the Union. This presentation is the one to look out for as a developer in my opinion. It's where Apple will dig deeper into the APIs and Frameworks that were introduced, and they'll show off new OS features in-depth.

I know that a lot of developers skip the State of the Union in favor of playing with new APIs and resting up, but I personally love watching the State of the Union. It usually gets me much more pumped up for all the good stuff than the main Keynote does.

Tip 5: Prepare for your lab sessions

WWDC is a unique chance for developers to ask questions to Apple engineers directly. Once WWDC is underway we have the opportunity to request appointments with Apple engineers, and we can show them our projects and ask for input on ways to improve our code, solve bugs, or even just clarify how certain things are supposed to work. Labs is arguably the most valuable part of WWDC, so when WWDC went online last year it was a relieve to see that labs were brought online too. And this year is no different.

When you book a lab appointment, try to make sure you come as prepared as possible. If you want to ask about (what you believe to be) a missing feature, or a bug, make sure you file feedback in Apple's feedback assistant ahead of time. This will make it so your lab session can be associated with the feedback which makes the Apple engineer's job a lot easier. It'll also allow the engineer to pull up your feedback ahead of time.

Possibly more important than filing feedback is that you make sure you have a demo project ready to show off. If your lab starts and you have to work your way through your full codebase, it will make it harder for you and the engineer to work through your question, and potentially fix your problem.

A bare-bones example of what you need will improve this a lot, and will help you make the most out of your lab appointment.

Tip 6: File feedback

This tip applies mostly to the period after WWDC. Once WWDC is over and we start spending actual time with new APIs, and we start testing our own apps on the latest Xcode version, bug will start showing themselves. Presented APIs might be missing features that you'd love to have, iOS 15 might do interesting things to your app, or maybe Xcode 13 crashes every time you do something specific.

No matter what your feedback is, file it as soon as you can.

Don't expect that others will already have filed your feedback. Even if they have, the more duplicates Apple gets, the more likely it is that your feedback is eventually bumped to the top of the list. This means that it's more likely that your bugs get fixed, and that your feature requests get implemented when you file your feedback.

I know that Apple has a history of not always responding to feedback, and that it can be very time consuming to file feedback. But if there is a perfect time for you to do it, it's during the big beta cycle that starts with WWDC.

In Summary

WWDC is a hectic time for everybody. I think it's most important that you focus on taking it easy, not giving in to that fear of missing out, and not trying to learn everything in one week. Consume WWDC content at your own pace, and focus on what's relevant to you unless it's simply something you're super interested in even though it might not be something that you can apply in your work just yet.

I hope that these tips help you make the most of your WWDC experience, and that you'll be able to enjoy this WWDC as much as I've always enjoyed mine!

What’s the difference between a singleton and a shared instance in Swift?

A common pattern on iOS, and in Swift, is to define an instance of an object that you can access from any place in your app. Common examples are URLSession.shared, FileManager.default, and UserDefaults.standard.

These objects can all be considered shared instances, or globally available instances. Defining a shared instance is commonly done as follows:

struct DataProvider {
  static let shared = DataProvider()

  // useful properties and methods
}

It's common for developers to call this a singleton, or a singleton instance.

The singleton pattern is a pattern that allows developers to specify an object that can only ever have one instance within the context of an application. This brings us to the reason that this DataProvider is not an actual singleton object.

There's nothing stopping you from writing the following code:

let providerOne = DataProvider()
let providerTwo = DataProvider()

Since we can create more instances of DataProvider, this means that DataProvider.shared is not a singleton. It's simply a convenient, shared, instance of DataProvider that's associated with the DataProvider type. This means that we can easily access this shared instance through DataProvider.shared.

If we wanted to write a more correct singleton object, we could make the initializer for DataProvider private. This would mean that we can no longer create new instances of DataProvider:

struct DataProvider {
  static let shared = DataProvider()

  // useful properties and methods

  private init() {
    // now we can only create new instances within DataProvider
  }
}

Of course, there's still a possibility that you add an extra static property to DataProvider and that you create more than one instance that way.

Personally, I would consider the above implementation good enough though, and I think you understand the point.

One important note about this example of a singleton (and the shared instance too for that matter) is that even though a static property is evaluated lazily, it's thread-safe. This means that we're guaranteed that the assigningment to the static property is atomic, and that we don't end up with multiple instances even if we access the property from multiple threads at the same time.

To learn more about how this works, check out this post from Jesse Squires. If you're not sure what atomic means in programming, read this post on my website.

Also, big thanks to Josh Asbury for telling me more about how static let is assigned atomically, and pointing me to Jesse's site.

In summary, a singleton is an object that you can only have one instance of. A shared instance is a convenience instance but it's not necessarily the only instance of an object.

An extensive guide to sorting Arrays in Swift

When you're working with Arrays in Swift, it's likely that you'll want to sort them at some point. In Swift, there are two ways to sort an Array:

  1. Through the Comparable implementation for each element in your array
  2. By providing a closure to perform a manual/specialized comparison between elements

If you have a homogenous array of elements, for example [String], you can rely on String's implementation of Comparable to sort an array of String in some sensible manner. There are two ways to sort an array of Comparable elements:

var strings = ["Oh", "Hello", "World", "This", "Is", "An", "Unsorted", "Array"]

// sort in-place
strings.sort()

// create a new array with sorted elements
let sorted = strings.sorted()

If you're not familiar with the Comparable protocol in Swift, take a look at the documentation for this protocol. In short, objects that are Comparable can be compared using < to determine order and with == to determine equality. If two elements are equal according to == then comparing them with < is expected to be false. This means that you can sort an array through the Comparable protocol because that provides all the tools to determine order.

The output from the code sample above is the following:

["An", "Array", "Hello", "Is", "Oh", "This", "Unsorted", "World"]

Based on this output, you can derive that String's default Comparable implementation sorts elements in increasing alphabetical order.

Whenever Swift's default sorting algorithm encounters an element that is equal to another element, it will preserve the original order of these elements in the output. In other words, Swift's sorting algorithm is a so-called stable sorting algorithm. If you want to learn more about how Swift's sort() is implemented, it uses an algorithm called IntroSort and you can learn more about it in this Swift Algorithms Club article.

If the default sorting behaviour for String isn't the behavior you're looking for, or you want to sort an array of items that aren't Comparable, you can use the sort(by:) and sorted(by:) sorting methods to take control of how your array is sorted. The difference between sort(by:) and sorted(by:) is the same as the difference between sort() and sorted(); the former sorts the array in-place, the latter returns a new array.

Here's an example of how sorted(by:) can be used to sort an array of strings by their length in descending order:

let sortedByLength = strings.sorted(by: { lhs, rhs in
  return lhs.count > rhs.count
})

The closure that's passed to sorted(by:) is called as often as needed with two elements to determine their order and you are expeced to return true if the first element (lhs) should precede the second element (rhs) is in the final result.

Because this sorting method is defined as O(n log n) you should be cautious about the amount of work you do in your closure. Doing too much work will result in a very slow sort and you shouldn't want that. This also applies to the regular sort() implementation, so if you're implementing your own Comparable conformance to make sorting an array of your own structs more straightforward, make sure your implementation is as efficient as possible.

Tip: If you're not familiar with Big-O notation, take a look at this post to learn more.

I mentioned earlier that if Swift's sorting algorithm encounters two equal elements, this also applies when you use sorted(by:) even though you can't express equality (you either return true or false). Swift's sorting algorithm processes your array in-order so it knows that if you return false from your sorting closure, an element should not precede the element that it's being compared to. If they're equal, any subsequent comparisons between both elements will return the same value, which means that these two elements will not relative positions unless they're not equal.

For example, sorting our original array while printing all performed comparisons yields this result:

Hello > Oh: true
World > Oh: true
World > Hello: false
This > Oh: true
This > World: false
Is > Oh: false
An > Is: false
Unsorted > An: true
Unsorted > Is: true
Unsorted > Oh: true
Unsorted > This: true
Unsorted > World: true
Unsorted > Hello: true
Array > An: true
Array > Is: true
Array > Oh: true
Array > This: true
Array > World: false

This output gives us a lot of insight into what happens. First Oh and Hello are compared and I return true. This means that Hello should precede Oh. This puts Hello at the first position in the sorted array and Oh is at the second position. Next, the third item in the array (World) is compared to the second (Oh). I return true Because World should precede Oh. Next, the algorithm wants to know whether World should precede Hello. They're equal in length so I return false ("Hello".count > "World".count is false).

The next interesting lines are:

Unsorted > World: true
Unsorted > Hello: true

The word Unsorted is longer than both World and Hello so it preceded both words. However, placing Unsorted before Hello does not change the order in which Hello and World appear in the final result.

The last comparison in our output is Array > World. This comparison returns false because these words are of equal length. And because Array occurs after World in the original, that order is maintained; we stop moving Array upward in the final result as soon as we return false.

While this experiment was useful to provide you with a basic understanding of how Swift's sorting works at the time of writing, you shouldn't rely on the details too much. Your main takeways should be:

  • Swift's sorting is stable; this means that equal elements keep their relative order
  • Swift's sorting is O(n log n) at worst

And of course the most important takeaway by far is that you can sort an array of Comparable elements with sort() and sorted(). If you need more control over how your elements are sorted, or if they aren't Comparable you can use sort(by:) or sorted(by:).

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

Splitting a JSON object into an enum and an associated object with Codable

Decoding data, like JSON, is often relatively straightforward. For a lot of use cases, you won't need to know or understand a lot more than what I explain in this post. However, sometimes you need to dive deeper into Codable, and you end up writing custom encoding or decoding logic like I explain in this post.

In more advanced scenarios, you might need to have an extremely flexible approach to decoding data. For example, you might want to decode your data into an enum that has an associated value depending on one or more properties that exist in your JSON data.

I know that this post sounds like it's most likely covered by this Swift evolution proposal. The proposal covers a slightly different case than the case that I'd like to show you in this post.

So before I dive in, I want to show you the problem that SE-0295 solves before I move on to the main topic of this post.

Understanding the problem that SE-0295 solves

Without going into too much detail, the SE-0295 Swift Evolution proposal covers coverting data that has a key + value pair into an enum with an associated value by using the key from the data as the enum case, and the value for a given key as an associated value.

So for example (this example is copied from the linked proposal), it would allow you to decode the following JSON:

{
  "load": {
    "key": "MyKey"
  }
}

Into the following Decodable enum:

enum Command: Decodable {
  case load(key: String)
}

This is neat, but it doesn't quite cover every possible use case.

For example, imagine that you have a JSON response that looks a bit like this:

{
  "coupons": [
    {
      "type": "promo",
      "discount": 0.2,
      "code": "AEHD36"
    },
    {
      "type": "giftcard",
      "value": 12.0,
      "code": "GIFT_2816"
    }
  ]
}

Decoding this into a Coupon enum that has a promo and giftcard case, both with associated values for the other fields, is not possible with SE-0295. Instead. We can use a custom init(from:) and encode(to:) to add support for this.

Decoding a JSON object into an enum case with associated values

Given the JSON from the previous section, I would like to decode this into the following Decodable models:

struct Checkout: Decodable {
  let coupons: [Coupon]
}

enum Coupon: Decodable {
  case promo(discount: Float, code: String)
  case giftcard(value: Float, code: String)
}

When you write these models, you'll immediately see that Xcode complains. Coupon does not conform to Decodable because the compiler can't synthesize the init(from:) implementation for Coupon. Luckily, we can write this initializer ourselves:

enum Coupon: Decodable {
  case promo(discount: Float, code: String)
  case giftcard(value: Float, code: String)

  enum CodingKeys: String, CodingKey {
    case type, discount, code, value
  }

  enum CouponType: String, Decodable {
    case promo, giftcard
  }

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let type = try container.decode(CouponType.self, forKey: .type)
    let code = try container.decode(String.self, forKey: .code)

    switch type {
    case .promo:
      let discount = try container.decode(Float.self, forKey: .discount)
      self = .promo(discount: discount, code: code)
    case .giftcard:
      let value = try container.decode(Float.self, forKey: .value)
      self = .giftcard(value: value, code: code)
    }
  }
}

There's a lot to look at in this code snippet. If you haven't seen a custom implementation of init(from:) before, you might want to take a look at this post where I explain how you can write custom decoding and encoding logic for your Codable models.

The CodingKeys that I defined for this enum contains all of the keys that I might be interested in. These coding keys match up with the fields in my JSON, but they do not line up with my enum cases. That's because I'll use the value of the type in the data we're decoding to determine which enum case we're decoding. Since I'd like to have some type safety here, I decode the value of type into another enum. This enum only contains the cases that I consider valid and usually you'll find that they mirror your main enum's cases without their associated values.

In my init(from:) implementation you'll notice that I create a container using the CodingKeys. Next, I create two local variables that hold the type and code from the data that I'm decoding. Next, I use a switch to check what the value of type is. Based on the value of type I know which other value(s) to decode, and I know which enum case I should use for self.

This is pretty similar to how I added an other case to an enum in the post I linked to earlier.

This approach will work fine if our enum cases are simple. But what if we might have more complex objects that would total up to an unreasonable number of associated values?

In that case, you can use another Decodable object as each enum case's single associated value. Let's take a look at an updated version of the Coupon enum without the decoding logic:

enum Coupon: Decodable {
  case promo(PromoCode)
  case giftcard(Giftcard)

  enum CodingKeys: String, CodingKey {
    case type
  }
}

Both enum cases now have a single associated value that's represented by a struct. The CodingKeys object now has a single case; type. We'll write the two structs for the associated values first. After that, I'll show you the updated decoding logic for Coupon:

struct PromoCode: Decodable {
  let discount: Float
  let code: String
}

struct Giftcard: Decodable {
  let value: Float
  let code: String
}

These two structs shouldn't surprise you. They're just plain structs that match the JSON data that they're supposed to represent. The real magic is in the init(from:) for the Coupon:

// In Coupon
init(from decoder: Decoder) throws {
  let container = try decoder.container(keyedBy: CodingKeys.self)
  let type = try container.decode(CouponType.self, forKey: .type)

  let associatedContainer = try decoder.singleValueContainer()

  switch type {
  case .promo:
    let promo = try associatedContainer.decode(PromoCode.self)
    self = .promo(promo)
  case .giftcard:
    let card = try associatedContainer.decode(Giftcard.self)
    self = .giftcard(card)
  }
}

This init(from:) still extracts and switches on the type key from the data we're decoding. The real trick is in how I create instances of PromoCode and Giftcard using the single value container that I create right before the switch:

let promo = try associatedContainer.decode(PromoCode.self)
// and
let card = try associatedContainer.decode(Giftcard.self)

Instead of asking my container to decode an instance of PromoCode for a given key, I ask the decoder for a single value container. This is a new container based on all the data that we're decoding. I ask that container to decode a PromoCode. The reason I can't call container.decode on the original container to decode a PromoCode is that container.decode expects to decode an object for a key. This means that if you're thinking of it in terms of JSON, the data should look like this for container.decode to work:

{
  "type": "promo",
  "data": {
    "discount": 0.1,
    "code": "AJDK9"
  }
}

If we'd have data that looked like that, we would be able to decode PromoCode for a data key.

However, the data isn't nested; it's flat. So what we want is to decode two Swift objects for a single data object. To do that, you can pass create a second container that expects to extract a single value from the data. In this case, this means that we have one container based on our data to extract a type, and another container that's used to decode the full JSON object into the decodable object we're looking for. Pretty neat, right?

Once the associated value is decoded, I can assign the appropriate enum case and associated value to self, and the decoding is done. The error from earlier is unchanged in the updated code for Coupon.

With these shenanigans in place, we should also look at what the encoding logic for this enum with an associated object looks like.

Encoding an enum with an associated value into a single JSON object

Encoding the Coupon enum from the previous section unsurprisingly follows a similar pattern. We can first encode the type property, and then pass our encoder to the encode(to:) method on our associated value to encode the associated value. If you're following along, make sure you mark the Giftcard and PromoCode structs as Codable (or Encodable).

Let's look at the updated Coupon:

enum Coupon: Codable {
  case promo(PromoCode)
  case giftcard(Giftcard)

  enum CodingKeys: String, CodingKey {
    case type
  }

  init(from decoder: Decoder) throws {
    // existing implementation for init(from:)
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)

    switch self {
    case .promo(let promo):
      try container.encode("promo", forKey: .type)
      try promo.encode(to: encoder)
    case .giftcard(let card):
      try container.encode("giftcard", forKey: .type)
      try card.encode(to: encoder)
    }
  }
}

The encoding logic is hopefully pretty much what you expected. I check what self is, and based on that I encode a type. Next, I grab the associated value and call encode(to:) on the associated value with the Encoder that our instance of Coupon received. The end result is that both the type and the encoded associated value both exist on the same encoded object.

To check whether the encoding logic works as expected, you can take the JSON string from the beginning of this post, convert it to data, decode it, and then encode it again:

let decoder = JSONDecoder()
let checkout = try! decoder.decode(Checkout.self, from: Data(jsonString.utf8))

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(checkout)
print(String(data: data, encoding: .utf8)!)

The structure of the printed JSON will mirror that of the JSON you saw at the start of this post.

Good stuff!

In Summary

In this post, you saw how you can use some interesting techniques to decode a single JSON object (or any data object that can be decoded using Decodable) into multiple objects by passing around your Decoder to different objects. In this example, I used an enum with an associated value but I'm sure you can imagine that you could make this approach to decode into two or more structs if needed. I personally couldn't think of a good reason to do this so I decided to not cover it in this post. Just know that the approach would be identical to what you've seen in this post.

A huge thank you goes out to Josh Asbury for pointing out a couple of neat improvements to the init(from:) that I ended up using for the Coupon object.

If you want to learn more about interesting things that you can do with Codable, make sure to check out the codable category on this website.

Writing custom JSON encoding and decoding logic

The default behavior for Codable is often good enough, especially when you combine this with custom CodingKeys, it's possible to encode and decode a wide variety of JSON data without any extra work.

Unfortunately, there are a lot of situations where you'll need to have even more control. The reasons for needing this control are varied. You might want to flatten a deeply nested JSON structure into a single Codable object. Or maybe you want to assign a default value to a property if it's not possible to extract this value from the received JSON data. Or maybe you want to make your an NSManagedObject subclass work with Codable.

No matter what your reason for needing to implement custom JSON encoding or decoding logic is for your Codable objects, the approach is always the same. In this post you'll learn how you can implement a custom init(from:) to decode JSON data, and a custom encoding(using:) method to encode a Codable object to JSON data.

Implementing custom JSON decoding logic

Decoding JSON data into a Decodable object is done through a special initializer that's required by the Decodable protocol. The initializer is called init(from decoder: Decoder), or as I like to write it init(from:).

This initializer is normally generated for you, but you can also implement it yourself if you need an extremely high level of customization.

The init(from:) initializer receives an object that conforms to the Decoder protocol, and it could be a JSONDecoder but that's not guaranteed. Swift's Decodable protocol was designed so it could work with different kinds of data.

In your initializer, you'll obtain a container object that knows how to extract values from the Data that's being decoded using your CodingKeys to look up these values. Note that as soon as you define your own init(from:), Swift will no longer generate your CodingKeys enum for you (even though Swift will generate an init(from:) if you define your own CodingKeys).

Let's look at a simple example of a custom init(from:):

struct User: Decodable {
  enum CodingKeys: String, CodingKey {
    case id, fullName, isRegistered, email
  }

  let id: Int
  let fullName: String
  let isRegistered: Bool
  let email: String

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.id = try container.decode(Int.self, forKey: .id)
    self.fullName = try container.decode(String.self, forKey: .fullName)
    self.isRegistered = try container.decode(Bool.self, forKey: .isRegistered)
    self.email = try container.decode(String.self, forKey: .email)
  }
}

This User struct is fairly standard, and if you look at it there's nothing fancy happening here. My CodingKeys all use their generated raw value which means that I expect my JSON to perfectly mirror the properties in this struct. In the init(from:) initializer, I obtain an instance of KeyedCodingContainer by calling container(keyedBy:) on the Decoder object that was passed to my initializer. This will create an instance of KeyedCodingContainer that will use the raw values for the cases on CodingKeys to look up information in my JSON data.

I can then call decode(_:forKey:) on my container object to extract an object of a given Decodable type (for example Int, String, or Bool) from my (JSON) data using the key that I passed as the forKey argument.

So for example, self.id = try container.decode(Int.self, forKey: .id) will attempt to look up a value for the key "id", and try to cast it to an Int. This is repeated for all properties on my struct.

This initial example shows how you can decode data that's consistent and always follows the same format. But what happens if the data is slightly less consistent, and we might need to work with default values in case a certain key is missing from the source data.

Assigning fallback values using a custom init(from:) method

Imagine that you are given the following JSON:

[
  {
    "id": 10,
    "fullName": "Donny Wals",
    "isRegistered": true,
    "email": "[email protected]",
  },
  {
    "id": 11,
    "fullName": "Donny Wals",
    "email": "[email protected]",
  }
]

Note how this is an array of two objects. One has an isRegistered property, and the other doesn't. You could say well, that should be a Bool? then so if isRegistered is missing, its value will be nil. That would work just fine.

However, we have a special requirement. If isRegistered is missing from the JSON data, we want the value for this property to be false.

You could attempt to define your User struct like this:

struct User: Decodable { 
  let id: Int
  let fullName: String
  let isRegistered = false
  let email: String
}

Unfortunately, this produces the following warning:

Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten

That's a shame because we do want to use the isRegistered value from the JSON data if it's present. Luckily, we can achieve this through a custom init(from:) implementation.

struct User: Decodable {
  enum CodingKeys: String, CodingKey {
    case id, fullName, isRegistered, email
  }

  let id: Int
  let fullName: String
  let isRegistered: Bool
  let email: String

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.id = try container.decode(Int.self, forKey: .id)
    self.fullName = try container.decode(String.self, forKey: .fullName)
    self.isRegistered = try container.decodeIfPresent(Bool.self, forKey: .isRegistered) ?? false
    self.email = try container.decode(String.self, forKey: .email)
  }
}

Note that all my properties are defined as non-optional. That's because we know that isRegistered should always have a value, even if we didn't receive one in our JSON data.

In the init(from:) implementation, I use decodeIfPresent instead of decode to extract the value for isRegistered. I can't do this with decode because decode assumes that a value exists for the key that you pass, and it assumes that this value has the type that you're looking for. If no value exists for the given key, or this value can't be casted to the desired type, decode will throw a decoding error.

To work around this you could use try? and hide the error, but then you might be hiding far more important mistakes. A better solution to only decode a value if it exists is to use decodeIfPresent. This method will attempt to look up a value for the given key, and if no value was found this method will return nil. If a value was found but it can't be cast to the desired type, an error is thrown because that means your Data isn't structured as expected.

Because decodeIfPresent returns and optional value (in this case Bool? because I attempt to decode to Bool), you can use a nil-coalescing operator to assign a default value (?? false).

This example is relatively simple, but it's also quite powerful. It shows how you can leverage the convenient APIs that were designed for Decoder to decode Data into a Swift object without actually knowing which type of Data you're dealing with. It's easy to forget but none of the code in init(from:) is aware that we're decoding JSON data with a JSONDecoder.

Using a custom init(from:) implementation to future proof decoding for enums

You already know that enums in Swift can be Decodable (and Encodable) as long as their raw value matches the value used in your JSON data. However, you might run into trouble and decoding failures when your service returns an enum case that you didn't know about when you defined your model. Luckily, you can use a custom init(from:) to safely decode unkown enum cases into an other case with an associated value when you encounter an unkown value.

For example, imagine a Product struct that has a status property. This status is initially defined as follows:

enum Status: String, Decodable {
  case completed, inProgress
}

This is simple enough, and will work perfect as long as your back-end only returns "completed" or "inProgress" for the value of status on a product object.

However, in programming, we often have to account for the unkown. Especially if you do not control the server, or if your back-end is maintained by a different team, you might want to make sure your status can handle other values too. After all, you might not want your decoding to fail just because you encountered an unknown status string. Especially if your server team can't provide any guarantees about whether they might add new enum cases on the server side that you don't know about.

Imagine that you need to decode the following (partial) JSON data:

[
  {
    "status": "completed"
  },
  {
    "status": "inProgress"
  },
  {
    "status": "archived"
  }
]

There's a new, unkown "archived" status in this JSON data. Normally, decoding this data would fail because your Status enum is not aware of this new value. However, a custom init(from:) will help you decode this value into a new other(String) case:

enum Status: Decodable {
  case completed, inProgress
  case other(String)

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let value = try container.decode(String.self)

    switch value {
    case "completed": self = .completed
    case "inProgress": self = .inProgress
    default: self = .other(value)
    }
  }
}

Note that I've removed the String raw value for Status. That's because an enum with a raw value can't have enum cases with associated values.

In the custom init(from:), I use decoder.singleValueContainer() to obtain a container that will only decode a single value. In this case, that's the string that's uses as the value for my product's status property. I'll show you how I've defined Product in a moment to clarify this.

I ask the container to decode its single value into a String, and then I use a switch to check the value of this string, and I use it to assign the appropriate enum case to self. If I find an unkown value, I assign the decoded value as the associated type for other. This will ensure decoding always works, even if the back-end team adds a new value without hiding any errors. Instead, you can check for the other case in your code, and handle this case in a way that is appropriate for your app.

Here's what the Product struct and my decoding code looks like:

struct Product: Decodable {
  let status: Status
}

let decoder = JSONDecoder()
let products = try decoder.decode([Product].self, from: jsonData)

As you can see this all looks very standard. The main takeaway here is that you can use a single value container to extract the value of a property in your JSON that isn't a JSON object/dictionary. You'll mostly find yourself use decoder.singleValueContainer() in the context of decoding enums.

Using RawRepresentable as an alternative to enums when handling unknown values

As with many things in programming, there's more than one way to implement a future-proof Decodable model. In the previous section I showed you how to use an enum with an other case to allow the decoding of new, and unknown values. If you don't like this, you can use a slightly different approach that was pointed out to me by Ian keen.

This alternative approach involes using a struct that's RawRepresentable by a String, as well as Decodable with static values for your known values. Here's what that would look like:

struct Status: Decodable, RawRepresentable {
  typealias RawValue = String

  static let completed = Status(rawValue: "completed")
  static let inProgress = Status(rawValue: "inProgress")

  let rawValue: String

  init?(rawValue: String) {
    self.rawValue = rawValue
  }
}

The nice thing about this is that you don't need to write any custom decoding (or encoding) logic at all. If you make Status conform to Equatable, you could even write comparison logic that looks a lot like you're used to with enums:

if let product = products.first, product.status == .completed {
  print(product status is completed)
}

Personally, I don't have a strong preference for either approach in this case. If you need to handle cases where you got an unknown value explicitly, an other enum might be a little nicer since you could easily compare to other. If you can handle any value just fine and only want to make sure you can decode an unknown value, the RawRepresentable struct might be a little nicer. It's up to you to decide the better fit.

There are many more neat little tricks that you can do with custom decoders, but for now you know everything you need to know write custom decoders for the most common situations you might encounter.

Implementing custom JSON encoding logic

Now that you know about decoding data into a Decodable object, it only makes sense to take a look at encoding an Encodable object into data too. Note how I didn't say JSON data. I did that on purpose because both your custom init(from:) and encode(to:) work without knowing what the format of the data is.

The custom decoding logic that I've shown you earlier used a container object to extract and convert data into the required data types like String, Int, or even your own Decodable objects.

Let's see how we apply this knowledge to a custom encode(to:) method for the User struct that I've shown you in the section on decoding. Here's the User struct from the previous section with the encode(to:) method already added to it:

struct User: Codable {
  enum CodingKeys: String, CodingKey {
    case id, fullName, isRegistered, email
  }

  let id: Int
  let fullName: String
  let isRegistered: Bool
  let email: String

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.id = try container.decode(Int.self, forKey: .id)
    self.fullName = try container.decode(String.self, forKey: .fullName)
    self.isRegistered = try container.decodeIfPresent(Bool.self, forKey: .isRegistered) ?? false
    self.email = try container.decode(String.self, forKey: .email)
  }

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

The code in encode(to:) is very similar to the code in init(from:). The main difference is in how you obtain a container. When you encode a struct to an Encoder, you need to obtain a mutable container that uses your CodingKeys as its mapping from your Encodable to the output format (usually JSON). In the case of encoder.container(keyedBy: CodingKeys.self), obtaining a container can't fail so you don't prefix this call with try.

Because you'll be encoding values into the container, the container needs to be a var. You'll mutate the container every time you ask it to encode a value.

To encode values, you call encode(_:forKey:) with the property you want to encode, and what key this property should be decoded to.

Sometimes, you'll want to send your encoded data to a server, and this server might expect you to omit nil values from your output. If that's the case, you should use encodeIdPresent(_:forKey:). This method will check whether the provided value is nil, and if it is, the key/value pair will be omitted from the container's output.

Next, let's take a look at how the encode(to:) for the Status enum from the previous section should be written since the Swift compiler can't properly account for the other enum case.

enum Status: Codable {
  case completed, inProgress
  case other(String)

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let value = try container.decode(String.self)

    switch value {
    case "completed": self = .completed
    case "inProgress": self = .inProgress
    default: self = .other(value)
    }
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()

    switch self {
    case .completed: try container.encode("completed")
    case .inProgress: try container.encode("inProgress")
    case .other(let val): try container.encode(val)
    }
  }
}

As you might have expected, the implementation for encode(to:) looks similar to init(from:). The encode(to:) method obtains a single value container, and I use a switch to check the value of self so I can determine which string should be encoded by the single value container. If I encounter my .other case, I extract the associated value and tell my container to encode that associated value. This will make sure that we always properly encode and send our enum to the server (or that we can persist it to disc) without discarding the original unkown value.

Of course if you don't use an enum but instead opt to use the RawRepresentable alternative from the previous section, the encoding will work fine out of the box, just like the decoding did.

In Summary

In this post, you learned how you can override Swift's generated init(from:) and encode(to:) methods that are added for Decodable and Encodable objects respectively.

You learned that Swift uses a container object to read and write values from and to a JSON object, and you saw how you can use this container to extract and encode data. You also learned how you can leverage custom encoding and decoding logic to write enums that can decode cases that weren't known at the time of defining your enum. This is incredibly useful to make sure your code is as future proof as possible.

I also showed you an alternative to using an enum that's based on using a RawRepresentable struct that has static members for what would normally be your known enum cases. One of the benefits of this approach is that the Decodable and Encodable conformances can be generated by the compiler so you don't need to do any extra work.

In this post, you'll learn how you can use custom encoding and decoding logic to work with arbitrary enum cases that have associated values by passing around your Decoder object to decode the enum case and associated value seperately from the same underlying data object. This sounds similar to this Swift evolution proposal, but as you'll find out in the post it's quite different.

Another interesting thing you can do with a custom init(from:) is flattening nested data into a single struct, or expand a single struct into nested data using encode(to:). You'll learn how to do this in this post.

With the knowledge from this post you'll be able to implement highly customized JSON encoding and decoding flow up to the point where your JSON data and Codable object are almost nothing alike.

Customizing how Codable objects map to JSON data

In the introductory post for this series you learned the basics of decoding and encoding JSON to and from your Swift structs. In that post, you learned that your JSON object is essentially a dictionary, and that the JSON's dictionary key's are mapped to your Swift object's properties. When encoding, your Swift properties are used as keys in the encoded JSON output dictionary.

Unfortunately, we don't always have the luxury of a 1 to 1 mapping between JSON and our Swift objects.

For example, the JSON you're working with might use snake case (snake_case) instead of camel case (camelCase) for its keys. Of course, you could write your Decodable object's properties using snake case so they match the JSON you're decoding but that would lead to very unusual Swift code.

In addition to the case styling not being the same, your JSON data might even have completely different names for some things that you don't want to use in your Swift objects.

Fortunately, both of these situations can be solved by either setting a key decoding (or encoding) strategy on your JSONDecoder or JSONEncoder, or by specifying a custom list of coding keys that map JSON keys to the properties on your Swift object.

If you'd prefer to learn about mapping your codable objects to JSON in a video format, you can watch the video on YouTube:

Automatically mapping from and to snake case

When you're interacting with data from a remote source, it's common that this data is returned to you as a JSON response. Depending on how the remote server is configured, you might receive a server response that looks like this:

{
    "id": 10,
    "full_name": "Donny Wals",
    "is_registered": false,
    "email_address": "[email protected]"
}

This JSON is perfectly valid. It represents a single JSON object with four keys and several values. If you were to define this model as a Swift struct, it'd look like this:

struct User: Codable {
  let id: Int
  let full_name: String
  let is_registered: Bool
  let email_address: String
}

This struct is valid Swift, and if you would decode User.self from the JSON I showed you earlier, everything would work fine.

However, this struct doesn't follow Swift conventions and best practices. In Swift, we use camel case so instead of full_name, you'll want to use fullName. Here's what the struct would look like when all properties are converted to camel case:

struct User: Codable {
  let id: Int
  let fullName: String
  let isRegistered: Bool
  let emailAddress: String
}

Unfortunately, we can't use this struct to decode the JSON from earlier. Go ahead and try with the following code:

let jsonData = """
{
  "id": 10,
  "full_name": "Donny Wals",
  "is_registered": false,
  "email_address": "[email protected]"
}
""".data(using: .utf8)!

do {
  let decoder = JSONDecoder()
  let user = try decoder.decode(User.self, from: jsonData)
  print(user)
} catch {
  print(error)
}

You'll find that the following error is printed to the console:

keyNotFound(CodingKeys(stringValue: "fullName", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"fullName\", intValue: nil) (\"fullName\").", underlyingError: nil))

This error means that the JSONDecoder could not find a corresponding key in the JSON data for the fullName.

To make our decoding work, the simplest way to achieve this is to configure the JSONDecoder's keyDecodingStrategy to be .convertFromSnakeCase. This will automatically make the JSONDecoder map full_name from the JSON data to fullName in struct by converting from snake case to camel case.

Let's look at an updated sample:

do {
  let decoder = JSONDecoder()
  decoder.keyDecodingStrategy = .convertFromSnakeCase
  let user = try decoder.decode(User.self, from: jsonData)
  print(user)
} catch {
  print(error)
}

This will succesfully decode a User instance from jsonData because all instances of snake casing in the JSON data are automatically mapped to their camel cased counterparts.

If you need to encode an instance of User into a JSON object that uses snake casing, you can use a JSONEncoder like you would normally, and you can set its keyEncodingStrategy to convertToSnakeCase:

do {
  let user = User(id: 1337, fullName: "Donny Wals",
                  isRegistered: true,
                  emailAddress: "[email protected]")

  let encoder = JSONEncoder()
  encoder.keyEncodingStrategy = .convertToSnakeCase
  let data = try encoder.encode(user)

  print(String(data: data, encoding: .utf8)!)
} catch {
  print(error)
}

The output for this code is the following:

{"id":1337,"full_name":"Donny Wals","email_address":"[email protected]","is_registered":true}

The ability to automatically convert from/to snake case is really useful when you're dealing with a server that uses snake case instead of camel casing.

Using a custom key decoding strategy

Since there is no single standard for what a JSON response should look like, some servers use arbitrary patterns for their JSON keys. For example, you might encounter service that uses keys that look like this: USER_ID. In that case, you can specify a custom key encoding- or decoding strategy.

Let's take a look at a slightly modified version of the JSON you saw earlier:

{
  "ID": 10,
  "FULL_NAME": "Donny Wals",
  "IS_REGISTERED": false,
  "EMAIL_ADDRESS": "[email protected]"
}

Since these keys all follow a nice and clear pattern, we can specify a custom strategy to convert our keys to lowercase, and then convert from snake case to camel case. Here's what that would look like:

do {
  let decoder = JSONDecoder()

  // assign a custom strategy
  decoder.keyDecodingStrategy = .custom({ keys in
    return FromUppercasedKey(codingKey: keys.first!)
  })

  let user = try decoder.decode(User.self, from: uppercasedJson)
  print(user)
} catch {
  print(error)
}

The closure that's passed to the custom decoding strategy takes an array of keys, and it's expected to return a single key. We're only interested in a single key so I'm grabbing the first key here, and I use it to create an instance of FromUppercasedKey. This object is a struct that I defined myself, and it conforms to CodingKey which means I can return it from my custom key decoder.

Here's what that struct looks like:

struct FromUppercasedKey: CodingKey {
  var stringValue: String
  var intValue: Int?

  init?(stringValue: String) {
    self.stringValue = stringValue
    self.intValue = nil
  }

  init?(intValue: Int) {
    self.stringValue = String(intValue)
    self.intValue = intValue
  }

  // here's the interesting part
  init(codingKey: CodingKey) {
    var transformedKey = codingKey.stringValue.lowercased()
    let parts = transformedKey.split(separator: "_")
    let upperCased = parts.dropFirst().map({ part in
      return part.capitalized
    })

    self.stringValue = (parts.first ?? "") + upperCased.joined()
    self.intValue = nil
  }
}

Every CodingKey must have a stringValue and an optional intValue property, and two initializers that either take a stringValue or an intValue.

The interesting part in my custom CodingKey is init(codingKey: CodingKey).

This custom initializer takes the string value for the coding key it received and transforms it to lowercase. After that I split the lowercased string using _. This means that FULL_NAME would now be an array that contains the words full, and name. I look over all entries in that array, except for the first array and I captialize the first letter of each word. So in the case of ["full", "name"], upperCased would be ["Name"]. After that I can create a string using the first entry in my parts array ("full"), and add the contents of the uppercase array after it (fullName). The result is a camel cased string that maps directly to the corresponding property in my User struct.

Note that the work I do inside init(codingKey: CodingKey) isn't directly related to Codable. It's purely string manipulation to convert strings that look like FULL_NAME to strings that look like fullName.

This example is only made to work with decoding. If you want it to work with encoding you'll need to define a struct that does the opposite of the struct I just showed you. This is slightly more complex because you'll need to find uppercase characters to determine where you should insert _ delimiters to match the JSON that you decoded initially.

A custom key decoding strategy is only useful if your JSON response follows a predicatable format. Unfortunately, this isn't always the case.

And sometimes, there's nothing wrong with how JSON is structured but you just prefer to map the values from the JSON you retrieve to different fields on your Decodable object. You can do this by adding a CodingKeys enum to your Codable object.

Using custom coding keys

Custom coding keys are defined on your Codable objects as an enum that's nested within the object itself. They are mostly useful when you want your Swift object to use keys that are different than the keys that are used in the JSON you're working with.

It's not uncommon for a JSON response to contain one or two fields that you would name differently in Swift. If you encounter a situation like that, it's a perfect reason to use a custom CodingKeys enum to specify your own set of coding keys for that object.

Consider the following JSON:

{
  "id": 10,
  "fullName": "Donny Wals",
  "registered": false,
  "e-mail": "[email protected]",
}

This JSON is slightly messy, but it's not invalid by any means. And it also doesn't follow a clear pattern that we can use to easily transform all keys that follow a specific pattern to something that's nice and Swifty. There are two fields that I'd want to decode into a property that doesn't match the JSON key: registered and e-mail. These fields should be decoded as isRegistered and email respectively.

To do this, we need to modify the User struct that you saw earlier in this post:

struct User: Codable {
  enum CodingKeys: String, CodingKey {
    case id, fullName
    case isRegistered = "registered"
    case email = "e-mail"
  }

  let id: Int
  let fullName: String
  let isRegistered: Bool
  let email: String
}

The only think that's changed in this example is that User now has a nested CodingKeys enum. This enum defines all keys that we want to extract from the JSON data. You must always add all keys that you need to this enum, so in this case I added id and fullName without a custom mapping. For isRegistered and email, I added a string that represents the JSON key that this property should map to. So isRegistered on User will be mapped to registered in the JSON.

To decode an object that uses custom coding keys, you don't need to do anything special:

do {
  let decoder = JSONDecoder()
  let user = try decoder.decode(User.self, from: jsonData)
  print(user)
} catch {
  print(error)
}

Swift will automatically use your CodingKeys enum when decoding your object from JSON, and it'll also use them when encoding your object to JSON. This is really convenient since there's nothing you need to do other than defining your CodingKeys.

Your CodingKeys enum must conform to the CodingKey playform and should use String as its raw value. It should also contain all your struct properties as its cases. If you omit a case, Swift won't be able to decode that property and you'd be in trouble. The case itself should always match the struct (or class) property, and the value should be the JSON key. If you want to use the same key for the JSON object as you use in your Codable object, you can let Swift infer the JSON key using the case name itself because enums will have a string representation of case as the case's raw value unless you specify a different raw value.

In Summary

In this post, you learned how you can use different techniques to map JSON to your Codable object's properties. First, I showed you how you can use built-in mechanisms like keyDecodingStrategy and keyEncodingStrategy to either automatically convert JSON keys to a Swift-friendly format, or to specificy a custom pattern to perform this transformation.

Next, you saw how you can customize your JSON encoding and decoding even further with a CodingKeys enum that provides a detailed mapping for your JSON <-> Codable conversion.

Using CodingKeys is very common when you work with Codable in Swift because it allows you to make sure the properties on your Codable object follow Swift best practices without enforcing a specific structure on your server data.