Iterating over web socket messages with async / await in Swift

In iOS 13, we gained the ability to easily send and receive data using web sockets through URLSession. With async/await, we gained the ability to fetch data from servers using the await keyword and we can iterate over asynchronous sequences using async for loops.

We can even read data from a URL one line at a time by calling the lines property on URL:

let url = URL(string: "https://donnywals.com")!

for try await line in url.lines {
    // use line
}

While this is really cool and allows us to build apps that ingest data in real time if the server supports streaming bodies, we cannot use the lines property to set up a web socket connection and listen for incoming messages and potentially send messages over the same connection too.

In this post, you will learn everything you need to know about building your own mechanism to conveniently iterate over messages from a web socket asynchronously. We will leverage some existing functionality from URLSessionWebSocketTask and AsyncThrowingStream to build our own AsyncSequence that conveniently wraps our URLSessionWebSocketTask.

Note that the resulting code has only had relatively limited testing done so I cannot guarantee that the provided solution will be 100% correct for everything you throw at it. If you find any issues with the final code, feel free to contact me. Bonus points if you’re able to provide some ideas for a potential fix.

Using a web socket without async / await

Before we get started, let's quickly review how to use a web socket without async/await. The code details are outlined in this post. Be sure to read it if you want to learn more about using web sockets in your apps.

let url = URL(string: "ws://127.0.0.1:8080")!
let socketConnection = URLSession.shared.webSocketTask(with: url)
socketConnection.resume()

func setReceiveHandler() {
    socketConnection.receive { result in
        defer { self.setReceiveHandler() }

        do {
            let message = try result.get()
            switch message {
            case let .string(string):
                print(string)
            case let .data(data):
                print(data)
            @unknown default:
                print("unkown message received")
            }
        } catch {
            // handle the error
            print(error)
        }
    }
}

setReceiveHandler()

Notice how, to receive messages from the socket, I must call receive with a completion handler. This method only allows me to receive a single incoming message, so I must re-set my handler after receiving a message to automatically begin listening for the next message.

This is a great example of a situation where an async for loop such as for try await message in socketConnection would make a lot of sense. Unfortunately, this is not possible out of the box. However, URLSessionWebSocketTask provides some form of support for async / await so we’re not entirely out of luck.

A basic implementation of web sockets with async / await

While URLSessionWebSocketTask doesn’t expose an AsyncSequence that emits incoming messages out of the box, it does come with an async version of the receive method you saw earlier.

This allows us to rewrite the example above as an async method as follows:

func setReceiveHandler() async {
    do {
        let message = try await socketConnection.receive()

        switch message {
        case let .string(string):
          print(string)
        case let .data(data):
          print(data)
        @unknown default:
          print("unkown message received")
        }
    } catch {
        print(error)
    }

    await setReceiveHandler()
}

This code works just fine, except we don’t really have a means to stop the recursion here. The code you saw earlier actually has the exact same issue; there’s no condition to stop listening for web socket messages even if the web socket connection has already been closed.

We could improve our code by only recursing if:

  1. We didn’t encounter any errors
  2. The socket connection is still active

This would look a bit as follows:

func setReceiveHandler() async {
    guard socketConnection.closeCode == .invalid else {
        return
    }

    do {
        let message = try await socketConnection.receive()

        switch message {
        case let .string(string):
          print(string)
        case let .data(data):
          print(data)
        @unknown default:
          print("unkown message received")
        }

        await setReceiveHandler()
    } catch {
        print(error)
    }
}

An open web socket’s closed code is always said to invalid to signal that the connection has not (yet) been closed. We can leverage this to check that our connection is still active before waiting for the next message to be received.

This is much better already because we respect closed sockets and failures much nicer now, but we could improve the readability of this code a tiny bit by leveraging a while loop instead of recursively calling the setReceiveHandler function:

func setReceiveHandler() async {
    var isActive = true

    while isActive && socketConnection.closeCode == .invalid {
        do {
            let message = try await socketConnection.receive()

            switch message {
            case let .string(string):
              print(string)
            case let .data(data):
              print(data)
            @unknown default:
              print("unkown message received")
            }
        } catch {
            print(error)
            isActive = false
        }
    }
}

To me, this version of the code is slightly easier to read but that might not be the case for you. It’s functionally equivalent so you can choose to use whichever option suits you best.

While this code works, I’m not quite happy with where we’ve landed right now. There’s a lot of logic in this function and I would prefer to separate handling the incoming values from the calls to socketConnection.receive() somehow. Ideally, I should be able to write the following:

do {
    for try await message in socketConnection {
        switch message {
        case let .string(string):
            print(string)
        case let .data(data):
            print(data)
        @unknown default:
            print("unkown message received")
      }
} catch {
    // handle error
}

This is much, much nicer from a call-site perspective and it would allow us to put the ugly bits elsewhere.

To do this, we can leverage the power of AsyncStream which allows us to build a custom async sequence of values.

Using AsyncStream to emit web socket messages

Given our end goal, there are a few ways for us to get where we want to be. The easiest way would be to write a function in an extension on URLSessionWebSocketTask that would encapsulate the while loop you saw earlier. This implementation would look as follows:

typealias WebSocketStream = AsyncThrowingStream<URLSessionWebSocketTask.Message, Error>

public extension URLSessionWebSocketTask {    
    var stream: WebSocketStream {
        return WebSocketStream { continuation in
            Task {
                var isAlive = true

                while isAlive && closeCode == .invalid {
                    do {
                        let value = try await receive()
                        continuation.yield(value)
                    } catch {
                        continuation.finish(throwing: error)
                        isAlive = false
                    }
                }
            }
        }
    }
}

To make the code a little bit easier to read, I’ve defined a typealias for my AsyncThrowingStream so we don’t have to look at the same long type signature all over the place.

The code above creates an instance of AsyncThrowingStream that asynchronously awaits new values from the web socket as long as the web socket is considered active and hasn't been closed. To emit incoming messages and potential errors, the continuation's yield and finish methods are used. These methods will either emit a new value (yield) or end the stream of values with an error (finish).

This code works great in many situations, but there is one issue. If we decide to close the web socket connection from the app's side by calling cancel(with:reason:) on our socketConnection, our WebSocketStream does not end. Instead, it will be stuck waiting for messages, and the call site will be stuck too.

Task {
    try await Task.sleep(for: .seconds(5))
    try await socketConnection.cancel(with: .goingAway, reason: nil)
}

Task {    
    do {
        for try await message in socketConnection.stream {
            // handle incoming messages
        }
    } catch {
        // handle error
    }

    print("this would never be printed")
}

If everything works as expected, our web socket connection will close after five seconds. At that point, our for loop should end and our print statement should execute, since the asynchronous stream is no longer active. Unfortunately, this is not the case, so we need to find a better way to model our stream.

URLSessionWebSocketTask does not provide a way for us to detect cancellation. So, I have found that it is best to use an object that wraps the URLSessionWebSocketTask, and to cancel the task through that object. This allows us to both end the async stream we are providing to callers and close the web socket connection with one method call.

Here’s what that object looks like:

class SocketStream: AsyncSequence {
    typealias AsyncIterator = WebSocketStream.Iterator
    typealias Element = URLSessionWebSocketTask.Message

    private var continuation: WebSocketStream.Continuation?
    private let task: URLSessionWebSocketTask

    private lazy var stream: WebSocketStream = {
        return WebSocketStream { continuation in
            self.continuation = continuation

            Task {
                var isAlive = true

                while isAlive && task.closeCode == .invalid {
                    do {
                        let value = try await task.receive()
                        continuation.yield(value)
                    } catch {
                        continuation.finish(throwing: error)
                        isAlive = false
                    }
                }
            }
        }
    }()

    init(task: URLSessionWebSocketTask) {
        self.task = task
        task.resume()
    }

    deinit {
        continuation?.finish()
    }

    func makeAsyncIterator() -> AsyncIterator {
        return stream.makeAsyncIterator()
    }

    func cancel() async throws {
        task.cancel(with: .goingAway, reason: nil)
        continuation?.finish()
    }
}

There’s a bunch of code here, but it’s not too bad. The first few lines are all about setting up some type aliases and properties for convenience. The lazy var stream is essentially the exact same code that you’ve already in the URLSessionWebSocketTask extension from before.

When our SocketStream's deinit is called we make sure that we end our stream. There’s also a cancel method that closes the socket connection as well as the stream. Because SocketStream conforms to AsyncSequence we must provide an Iterator object that’s used when we try to iterate over our SocketStreams. We simply ask our internal stream object to make an iterator and use that as our return value.

Using the code above looks as follows:

let url = URL(string: "ws://127.0.0.1:8080")!
let socketConnection = URLSession.shared.webSocketTask(with: url)
let stream = SocketStream(task: socketConnection)

Task {  
    do {
        for try await message in stream {
            // handle incoming messages
        }
    } catch {
        // handle error
    }

    print("this will be printed once the stream ends")
}

To cancel our stream after 5 seconds just like before, you can run the following task in parallel with our iterating task:

Task {
    try await Task.sleep(for: .seconds(5))
    try await stream.cancel()
}

Task {
    // iterate...
}

While this is pretty cool, we do have a bit of an issue here on older iOS versions because of the following bit of code. By older I mean pre-iOS 17.0.

If you're targetting iOS 17 or newer you can ignore this next part

private lazy var stream: WebSocketStream = {
    return WebSocketStream { continuation in
        self.continuation = continuation

        Task {
            var isAlive = true

            while isAlive && task.closeCode == .invalid {
                do {
                    let value = try await task.receive()
                    continuation.yield(value)
                } catch {
                    continuation.finish(throwing: error)
                    isAlive = false
                }
            }
        }
    }
}()

The task that we run our while loop in won’t end unless we end our stream from within our catch block. If we manually close the web socket connection using the cancel method we write earlier, the call to receive() will never receive an error nor a value which means that it will be stuck forever. This was fixed in iOS 17 but is still a problem in older iOS versions.

The most reliable way to fix this is to go back to the callback based version of receive to drive your async stream:

private lazy var stream: WebSocketStream = {
    return WebSocketStream { continuation in
        self.continuation = continuation
        waitForNextValue()
    }
}()

private func waitForNextValue() {
    guard task.closeCode == .invalid else {
        continuation?.finish()
        return
    }

    task.receive(completionHandler: { [weak self] result in
        guard let continuation = self?.continuation else {
            return
        }

        do {
            let message = try result.get()
            continuation.yield(message)
            self?.waitForNextValue()
        } catch {
            continuation.finish(throwing: error)
        }
    })
}

With this approach we don’t have any lingering tasks, and our call site is as clean and concise as ever; we’ve only changed some of our internal logic.

In Summary

Swift Concurrency provides many useful features for writing better code, and Apple quickly adopted async / await for existing APIs. However, some APIs that would be useful are missing, such as iterating over web socket messages.

In this post, you learned how to use async streams to create an async sequence that emits web socket messages. You first saw a fully async / await version that was neat, but had memory and task lifecycle issues. Then, you saw a version that combines a callback-based approach with the async stream.

The result is an easy way to iterate over incoming web socket messages with async / await. If you have any questions, comments, or improvements for this post, please don't hesitate to reach out to me on Twitter.

Expand your learning with my books

Practical Core Data header image

Learn everything you need to know about Core Data and how you can use it in your projects with Practical Core Data. It contains:

  • Twelve chapters worth of content.
  • Sample projects for both SwiftUI and UIKit.
  • Free updates for future iOS versions.

The book is available as a digital download for just $39.99!

Learn more

Understanding Swift Concurrency’s AsyncStream and AsyncThrowingStream

In an earlier post, I wrote about different ways that you can bridge your existing asynchronous code over to Swift’s new Concurrency system that leverages async / await. The mechanisms shown there work great for code where your code produces a single result that can be modeled as a single value.

Since writing this post we've gained AsyncStream.makeStream which makes stream creation a lot smoother. Learn more in this post.

However in some cases this isn’t possible because your existing code will provide multiple values over time. This is the case for things like download progress, the user’s current location, and other similar situations.

Generally speaking, these kinds of patterns would be modeled as AsyncSequence objects that you can iterate over using an asynchronous for loop. A basic example of this would be the lines property on URL:

let url = URL(string: "https://donnywals.com")!

for try await line in url.lines {
    // use line
}

But what’s the best way to build your own async sequences? Implementing the AsyncSequence protocol and building your on AsyncIterator sounds tedious and error-prone. Luckily, there’s no reason for you to be doing any of that.

In this post, I will show you how you can leverage Swift’s AsyncStream to build custom async sequences that produce values whenever you need them to.

Producing a simple async stream

An async stream can be produced in various ways. The easiest way to create an async stream is to use the AsyncStream(unfolding:) initializer. Its usage looks a bit as follows:

let stream = AsyncStream(unfolding: {
    return Int.random(in: 0..<Int.max)
})

Of course, this example isn’t particularly useful on its own but it does show how simple the concept of AsyncStream(unfolding:) is. We use this version of AsyncStream whenever we can produce and return return values for our async stream. The closure that’s passed to unfolding is async so this means that we can await asynchronous operations from within our unfolding closure. Your unfolding closure will be called every time you’re expected to begin producing a value for your stream. In practice this means that your closure will be called, you perform some work, you return a value and then your closure is called. This repeats until the for loop is cancelled, the task that contains your async for loop is cancelled, or until you return nil from your unfolding closure.

The AsyncStream(unfolding:) way to produce a stream of values is quite convenient but it’s particularly useful in situations where:

  • You want to perform async work that needs to be awaited to produce elements
  • You have a need to handle back pressure when bridging an API you own

When you’re bridging an existing API that’s based on delegates or for APIs that leverage callbacks to communicate results, you probably won’t be able to use AsyncStream(unfolding:). While it’s the simplest and least error-prone way to build an async stream, it’s also the way that I’ve found to be most limiting and it doesn’t often fit well with bridging existing code over to Swift Concurrency.

More flexibility can be found in the continuation based API for AsyncStream.

Producing an async stream with a continuation

When an asynchronous closure doesn’t quite fit your use case for creating your own async stream, a continuation based approach might be a much better solution for you. With a continuation you have the ability to construct an async stream object and send values over the async stream whenever values become available.

We can do this by creating an AsyncStream using the AsyncStream(build:) initializer:

let stream2 = AsyncStream { cont in
    cont.yield(Int.random(in: 0..<Int.max))
}

The example above creates an AsyncStream that produces a single integer value. This value is produced by calling yield on the continuation. Every time we have a value to send, we should call yield on the continuation with the value that we want to send.

If we’re building an AsyncStream that wraps a delegate based API, we can hold on to our continuation in the delegate object and call yield whenever a relevant delegate method is called.

For example, we could call continuation.yield from within a CLLocationManagerDelegate whenever a new user location is made available to us:

class AsyncLocationStream: NSObject, CLLocationManagerDelegate {
    lazy var stream: AsyncStream<CLLocation> = {
        AsyncStream { (continuation: AsyncStream<CLLocation>.Continuation) -> Void in
            self.continuation = continuation
        }
    }()
    var continuation: AsyncStream<CLLocation>.Continuation?

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        for location in locations {
            continuation?.yield(location)
        }
    }
}

The example above is a very naive starting point for creating an async stream of user locations. There are a couple of things we don’t fully take into account such as cancelling and starting location observation or asking for location permissions.

At its core though, this example is a great starting point for experimenting with async streams.

Note that this approach will not wait for consumers of your async stream to consume a value fully before you can send your next value down the stream. Instead, all values that you send will be buffered in your async stream by default which may or may not be what you want.

In practical terms this means that when you send values down your stream faster than the consuming for loop can process these values, you will end up with a buffer filled with values that will be delivered to the consuming for loop with a delay. This might be exactly what you need, but if the values you send are somewhat time sensitive and ephemeral it would potentially make sense to drop values if the consuming for loop isn’t ready to receive values.

We could decide that we never want to hold on to more than 1 location and that we only want to buffer the last known location to avoid processing stale data. We can do this by setting a buffering policy on our async stream:

lazy var stream: AsyncStream<CLLocation> = {
    AsyncStream(bufferingPolicy: .bufferingNewest(1)) { (continuation: AsyncStream<CLLocation>.Continuation) -> Void in
        self.continuation = continuation
    }
}()

This code passes a bufferingPolicy of .bufferingNewest(1) to our AsyncStream. This means that we will only buffer a single value if the consuming for loop isn’t processing items fast enough, and we will discard older values in favor of keeping only the latest location.

If our stream comes to a natural close, you can call finish() on your continuation to end the stream of values.

If your stream might fail with an error, you can also choose to create an AsyncThrowingStream instead of an AsyncStream. The key difference is that consumers of a throwing stream must await new values using try await instead just await. To make your stream throw an error you can either call finish(throwing:) on your continuation or you can call yield(with:) using a Result object that represents a failure.

While the basics of building an AsyncStream aren’t particularly complex, we do need to think about how we manage the lifecycles of the things we create carefully. Especially because we’re not supposed to make our continuations outlive our streams which is a very easy mistake to make when you’re bridging existing delegate based code.

Managing your stream’s lifecycle

There are essentially two ways for an async stream to end. First, the stream might naturally end producing values because no further values can be produced. You will call finish on your continuation and you can provide any cleanup that you need to do at the same time. For example, you could set the continuation that you’re holding on to to nil to make sure you can’t accidentally use it anymore.

Alternatively, your stream can end because the task that’s used to run your async stream is cancelled. Consider the following:

let locations = AsyncLocationStream()

let task = Task {
    for await location in locations.stream {
        print(location)
    }
}

task.cancel()

When something like the above happens, we will want to make sure that we don’t call yield on our continuation anymore unless we start a new stream with a new, active, continuation.

We can detect and respond to the end of our stream by setting an onTermination handler on our continuation:

self.continuation?.onTermination = { result in
    print(result)
    self.continuation = nil
}

Ideally we set this handler immediately when we first create our async stream.

In addition to the stream being cancelled or otherwise going out of scope, we could break out of our loop which will eventually cause our task to finish. This is generally speaking not something this will end your async stream so if you want breaking out of your loop to end your stream, you will need to take this into account yourself.

Personally, I’ve found that the easiest way to make sure you do some cleanup is to have some method on your stream producing object to cancel the stream instead of just breaking out of an async for loop. That way, you can perform cleanup and not have a stream that’s sending values even though nobody is listening.

It’s also important to bear in mind that the pattern I showed earlier will only work if one consumer uses your location stream object. You cannot have multiple for loops iterating over a single stream in Swift Concurrency because by default, async sequences lack the ability to share their iterations with multiple loops.

If you're interested in seeing a practical application of async streams to bridge existing code into Swift Concurrency, take a look at this post where I use AsyncStream to iterate over incoming web socket messages.

In Summary

In this post, you learned a lot about async streams and how you can produce your own async sequences. First, you saw the unfolding approach of building an async stream and you learned that this approach is relatively straightforward but might not be very useful for people that need to bridge existing delegate or callback based APIs.

After exploring unfolding for a bit, we took a look at the build closure for async streams. You learned that this approach leverages a continuation object that can be called to produce values if and when needed.

You saw a very rudimentary example of an object that would bridge a CLLocationManager into async await, and you learned a but about correctly managing your continuations to prevent sending values into an already completed stream.

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

Providing a default value for a SwiftUI Binding

Sometimes in SwiftUI apps I’ll find that I have a model with an optional value that I’d like to pass to a view that requires a non optional value. This is especially the case when you’re using Core Data in your SwiftUI apps and use auto-generated models. These models will usually include optional values for properties (even when your Core Data model marks these fields as required).

Consider the following example:

@Observable
class SearchService {
  var results: [SearchResult] = []
  var query: String?
}

Let me start by acknowledging that yes, this object can be written with a query: String = "" instead of an optional String?. Unfortunately, we don’t always own or control the models and objects that we’re working with. In these situations we might be dealing with optionals where we’d rather have our values be non-optional. Again, this can be especially true when using generated code (like when you’re using Core Data).

Now let’s consider using the model above in the following view:

struct MyView: View {
  @Bindable var searchService: SearchService

  var body: some View {
      TextField("Query", text: $searchService.query)
  }
}

This code will not compile because we need to pass a binding to a non optional string to our text field. The compiler will show the following error:

Cannot convert value of type Binding<String?> to expected argument type Binding<String>

One of the ways to fix this is to provide a custom instance of Binding that can provide a default value in case query is nil. Making it a Binding<String> instead of Binding<String?>.

If you're not familiar with the @Bindable property wrapper, I highly recommen you take a look at my comparison of Bindable and Binding.

Defining a custom binding

A SwiftUI Binding instance is nothing more than a get and set closure that are called whenever somebody tries to read the current value of a Binding or when we assign a new value to it.

Here’s how we can create a custom binding:

Binding(get: {
  return "Hello, world"
}, set: { _ in
  // we can update some external or captured state here
})

The example above essentially recreates Binding's .constant which is a binding that will always provide the same pre-determined value.

If we were to write a custom Binding that allows us to use $searchService.query to drive our TextField it would look a bit like this:

struct MyView: View {
  let searchService: SearchService

  var customBinding: Binding<String> {
    return Binding(get: {
      return searchService.query ?? ""
    }, set: { newValue in
      searchService.query = newValue
    })
  }

  var body: some View {
    TextField("Query", text: customBinding)
  }
}

This compiles, and it works well, but if we have several occurrences of this situation in our codebase, it would be nice if had a better way of writing this. For example, it would neat if we could write the following code:

struct MyView: View {
  @Bindable var searchService: SearchService

  var body: some View {
    TextField("Query", text: $searchService.query.withDefault(""))
  }
}

We can achieve this by adding an extension on Binding with a method that’s available on existing bindings to optional values:

extension Binding {
  func withDefault<T>(_ defaultValue: T) -> Binding<T> where Value == Optional<T> {
    return Binding<T>(get: {
      self.wrappedValue ?? defaultValue
    }, set: { newValue in
      self.wrappedValue = newValue
    })
  }
}

The withDefault(_:) function we wrote here can be called on Binding instances and in essence it does the exact same thing as the original Binding already did. It reads and writes the original binding’s wrappedValue. However, if the source Binding has nil value, we provide our default.

What’s nice is that we can now create bindings to optional values with a pretty straightforward API, and we can use it for any kind of optional data.

There's one (sometimes major) downside to this approach that's related to animations. A custom binding can, in some cases, break your animations. If you're using a binding alongside withAnimation, it's likely that you're running into some issues related to custom bindings. If this happens, you should update your data model, or prevent having a direct mapping between your state and optional objects:

struct MyView: View {
  let searchService: SearchService
  @State var query = ""

  var body: some View {
      TextField("Query", text: $query)
        .onChange(of: query) { 
          searchService.query = query
        }
  }
}

It's not ideal, and the result is that we no longer have a two-way binding. In some cases that's fine, but if you do require a two-way binding you'll want to figure out a way to update your data model so that there's no longer a need to bind to an optional value.

Enabling Concurrency warnings in Xcode 16

If you want to make sure that your code adopts Swift concurrency as correctly as possible in Swift 5.x, it's a good idea to enable the Strict Concurrency Checking (SWIFT_STRICT_CONCURRENCY) flag in your project.

To do this, select your project's target and navigate to the Build Settings tab. Make sure you select All from the list of settings that is shown (Basic is the default) and type Strict Concurrency in the searchbar to find the Strict Concurrency Checking build setting.

The screenshot below shows all the relevant parts for you to see:

Screenshot of the strict concurrency checking settings

The default value for this setting is Minimal which boils down to the Compiler only checking explicit Sendable annotations amongst other things. This setting is the least restrictive and enforces as little of Swift Concurrency's constraints as possible for the time being.

You can bump your checking to Targeted which will enforce Sendable and actor-isolation checks in your code, and it will explicitly verify that Sendable constraints are met when you mark one of your types as Sendable. This mode is essentially a bit of a hybrid between the behavior that's intended in Swift 6, and what's allowed now. You can use this mode to have a bit of checking on your code that uses Swift Concurrency without too much warnings and / or errors in your current codebase.

With Complete you will get the full suite of concurrency constraints, essentially as they will work in Swift 6. Personally I would recommend enabling this setting for new projects where you want all of your code to be properly checked immediately. In an existing codebase this mode might be a little too strict, but on the other hand it will flag lots of things that will be mandatory in Swift 6.

Enabling strict concurrency for Swift Packages

When you're working with Swift packages, you won't be able to turn on strict concurrency through the UI. Instead, you'll have to update your Package.swift as follows:

let package = Package(
    name: "AppCore",
    platforms: [.iOS(.v17)],
    products: [
        .library(
            name: "AppCore",
            targets: ["AppCore"]),
    ],
    targets: [
        .target(
            name: "AppCore",
            swiftSettings: [
                .enableExperimentalFeature("StrictConcurrency")
            ]
        )
    ]
)

Notice the following lines that I passed to my target:

swiftSettings: [
  .enableExperimentalFeature("StrictConcurrency")
]

Adding this setting will make your package compile with strict concurrency checks enabled.

If you're working on a Swift Package created with Xcode 16, you might have to set your Swift language version back to Swift 5 if you're not ready for Swift 6 yet.

Update: At the moment, it looks like Apple will allow developers to continue using Sendability checking from Swift 5 with the Swift 6 compiler for a very long time. To me, this says that there's still some gaps and complexities in getting all codebases to be compliant with strict Sendable checks.

What are Sendable and @Sendable closures in Swift?

One of the goals of the Swift team with Swift’s concurrency features is to provide a model that allows developer to write safe code by default. This means that there’s a lot of time and energy invested into making sure that the Swift compiler helps developers detect, and prevent whole classes of bugs and concurrency issues altogether.

One of the features that helps you prevent data races (a common concurrency issue) comes in the form of actors which I’ve written about before.

While actors are great when you want to synchronize access to some mutable state, they don’t solve every possible issue you might have in concurrent code.

In this post, we’re going to take a closer look at the Sendable protocol, and the @Sendable annotation for closures. By the end of this post, you should have a good understanding of the problems that Sendable (and @Sendable) aim to solve, how they work, and how you can use them in your code.

Note: If you're here to learn about closure than be safely executed in multiple concurrency contexts; I highly recommend that you read my comparison of sending and sendable closures.

Understanding the problems solved by Sendable

One of the trickiest aspects of a concurrent program is to ensure data consistency. Or in other words, thread safety. When we pass instances of classes or structs, enum cases, or even closures around in an application that doesn’t do much concurrent work, we don’t need to worry about thread safety a lot. In apps that don’t really perform concurrent work, it’s unlikely that two tasks attempt to access and / or mutate a piece of state at the exact same time. (But not impossible)

For example, you might be grabbing data from the network, and then passing the obtained data around to a couple of functions on your main thread.

Due to the nature of the main thread, you can safely assume that all of your code runs sequentially, and no two processes in your application will be working on the same referencea at the same time, potentially creating a data race.

To briefly define a data race, it’s when two or more parts of your code attempt to access the same data in memory, and at least one of these accesses is a write action. When this happens, you can never be certain about the order in which the reads and writes happen, and you can even run into crashes for bad memory accesses. All in all, data races are no fun.

Why actors don't solve all our problems

While actors are a fantastic way to build objects that correctly isolate and synchronize access to their mutable state, they can’t solve all of our data races. And more importantly, it might not be reasonable for you to rewrite all of your code to make use of actors.

Consider something like the following code:

class FormatterCache {
    var formatters = [String: DateFormatter]()

    func formatter(for format: String) -> DateFormatter {
        if let formatter = formatters[format] {
            return formatter
        }

        let formatter = DateFormatter()
        formatter.dateFormat = format
        formatters[format] = formatter

        return formatter
    }
}

func performWork() async {
    let cache = FormatterCache()
    let possibleFormatters = ["YYYYMMDD", "YYYY", "YYYY-MM-DD"]

    await withTaskGroup(of: Void.self) { group in
        for _ in 0..<10 {
            group.addTask {
                let format = possibleFormatters.randomElement()!
                let formatter = cache.formatter(for: format)
            }
        }
    }
}

On first glance, this code might not look too bad. We have a class that acts as a simple cache for date formatters, and we have a task group that will run a bunch of code in parallel. Each task will grab a random date format from the list of possible format and asks the cache for a date formatter.

Ideally, we expect the formatter cache to only create one date formatter for each date format, and return a cached formatter after a formatter has been created.

However, because our tasks run in parallel there’s a chance for data races here. One quick fix would be to make our FormatterCache an actor and this would solve our potential data race. While that would be a good solution (and actually the best solution if you ask me) the compiler tells us something else when we try to compile the code above:

Capture of 'cache' with non-sendable type 'FormatterCache' in a @Sendable closure

This warning is trying to tell us that we’re doing something that’s potentially dangerous. We’re capturing a value that cannot be safely passed through concurrency boundaries in a closure that’s supposed to be safely passed through concurrency boundaries. To learn more about this warning, you can take a look at my guide to solve similar errors related to capturing non-sendable state.

⚠️ If the example above does not produce a warning for you, you'll want to enable strict concurrency checking in your project's build settings for stricter Sendable checks (amongst other concurrency checks). You can enable strict concurrecy settings in your target's build settings. Take a look at this page if you're not sure how to do this.

Being able to be safely passed through concurrency boundaries essentially means that a value can be safely accessed and mutated from multiple tasks concurrently without causing data races. Swift uses the Sendable protocol and the @Sendable annotation to communicate this thread-safety requirement to the compiler, and the compiler can then check whether an object is indeed Sendable by meeting the Sendable requirements.

What these requirements are exactly will vary a little depending on the type of objects you deal with. For example, actor objects are Sendable by default because they have data safety built-in.

Let’s take a look at other types of objects to see what their Sendable requirements are exactly.

Sendable and value types

In Swift, value types provide a lot of thread safety out of the box. When you pass a value type from one place to the next, a copy is created which means that each place that holds a copy of your value type can freely mutate its copy without affecting other parts of the code.

This a huge benefit of structs over classes because they allow use to reason locally about our code without having to consider whether other parts of our code have a reference to the same instance of our object.

Because of this behavior, value types like structs and enums are Sendable by default as long as all of their members are also Sendable.

Let’s look at an example:

// This struct is not sendable
struct Movie {
    let formatterCache = FormatterCache()
    let releaseDate = Date()
    var formattedReleaseDate: String {
        let formatter = formatterCache.formatter(for: "YYYY")
        return formatter.string(from: releaseDate)
    }
}

// This struct is sendable
struct Movie {
    var formattedReleaseDate = "2022"
}

I know that this example is a little weird; they don’t have the exact same functionality but that’s not the point.

The point is that the first struct does not really hold mutable state; all of its properties are either constants, or they are computed properties. However, FormatterCache is a class that isn't Sendable. Since our Movie struct doesn’t hold a copy of the FormatterCache but a reference, all copies of Movie would be looking at the same instances of the FormatterCache, which means that we might be looking at data races if multiple Movie copies would attempt to, for example, interact with the formatterCache.

The second struct only holds Sendable state. String is Sendable and since it’s the only property defined on Movie, movie is also Sendable.

The rule here is that all value types are Sendable as long as their members are also Sendable.

Generally speaking, the compiler will infer your structs to be Sendable when needed. However, you can manually add Sendable conformance if you'd like:

struct Movie: Sendable {
    let formatterCache = FormatterCache()
    let releaseDate = Date()
    var formattedReleaseDate: String {
        let formatter = formatterCache.formatter(for: "YYYY")
        return formatter.string(from: releaseDate)
    }
}

Sendable and classes

While both structs and actors are implicitly Sendable, classes are not. That’s because classes are a lot less safe by their nature; everybody that receives an instance of a class actually receives a reference to that instance. This means that multiple places in your code hold a reference to the exact same memory location and all mutations you make on a class instance are shared amongst everybody that holds a reference to that class instance.

That doesn’t mean we can’t make our classes Sendable, it just means that we need to add the conformance manually, and manually ensure that our classes are actually Sendable.

We can make our classes Sendable by adding conformance to the Sendable protocol:

final class Movie: Sendable {
    let formattedReleaseDate = "2022"
}

The requirements for a class to be Sendable are similar to those for a struct.

For example, a class can only be Sendable if all of its members are Sendable. This means that they must either be Sendable classes, value types, or actors. This requirement is identical to the requirements for Sendable structs.

In addition to this requirement, your class must be final. Inheritance might break your Sendable conformance if a subclass adds incompatible overrides or features. For this reason, only final classes can be made Sendable.

Lastly, your Sendable class should not hold any mutable state. Mutable state would mean that multiple tasks can attempt to mutate your state, leading to a data race.

However, there are instances where we might know a class or struct is safe to be passed across concurrency boundaries even when the compiler can’t prove it.

In those cases, we can fall back on unchecked Sendable conformance.

Unchecked Sendable conformance

When you’re working with codebases that predate Swift Concurrency, chances are that you’re slowly working your way through your app in order to introduce concurrency features. This means that some of your objects will need to work in your async code, as well as in your sync code. This means that using actor to isolate mutable state in a reference type might not work so you’re stuck with a class that can’t conform to Sendable. For example, you might have something like the following code:

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

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

            let formatter = DateFormatter()
            formatter.dateFormat = format
            formatters[format] = formatter

            return formatter
        }
    }
}

This formatter cache uses a serial queue to ensure synchronized access to its formatters dictionary. While the implementation isn’t ideal (we could be using a barrier or maybe even a plain old lock instead), it works. However, we can’t add Sendable conformance to our class because formatters isn’t Sendable.

To fix this, we can add @unchecked Sendable conformance to our FormatterCache:

class FormatterCache: @unchecked Sendable {
    // implementation unchanged
}

By adding this @unchecked Sendable we’re instructing the compiler to assume that our FormatterCache is Sendable even when it doesn’t meet all of the requirements.

Having this feature in our toolbox is incredibly useful when you’re slowly phasing Swift Concurrency into an existing project, but you’ll want to think twice, or maybe even three times, when you’re reaching for @unchecked Sendable. You should only use this feature when you’re really certain that your code is actually safe to be used in a concurrent environment.

Using @Sendable on closures

There’s one last place where Sendable comes into play and that’s on functions and closures.

A sendable closure can run in a concurrent manner so we want to make sure that we’re not accidentally introducing a data race. If all state captured by the closure is Sendable, then we know for sure that the closure itself is Sendable. Or in other words, we know that the closure can safely be passed around in a concurrent environment.

An example of a function that accepts a @Sendable closure looks like this:

func performWork(_ operation: @escaping @Sendable () async)

A more convenient and preferred way to deal with closure that must be safe to execute was added in Swift 6 with the sending keyword. If you're writing an API that takes closures that must be safe to execute concurrently; I highly recommend that you study up on sending in Swift because it makes life much easier.

Tip: to learn more about closures in Swift, take a look at my post that explains closures in great detail.

Summary

In this post, you’ve learned about the Sendable and @Sendable features of Swift Concurrency. You learned why concurrent programs require extra safety around mutable state, and state that’s passed across concurrency boundaries in order to avoid data races.

You learned that structs are implicitly Sendable if all of their members are Sendable. You also learned that classes can be made Sendable as long as they’re final, and as long as all of their members are also Sendable.

Lastly, you learned that the @Sendable annotation for closures helps the compiler ensure that all state captured in a closure is Sendable and that it’s safe to call that closure in a concurrent context.

I hope you’ve enjoyed this post. If you have any questions, feedback, or suggestions to help me improve the reference then feel free to reach out to me on Twitter.

Xcode 14 “Publishing changes from within view updates is not allowed, this will cause undefined behavior”

UPDATE FOR XCODE 14.1: This issue appears to have been partially fixed in Xcode 14.1. Some occurences of the warning are fixed, others aren't. In this post I'm collecting situations me and others run into and track whether they are fixed or not. If you have another sample that you think is similar, please send a sample of your code on Twitter as a Github Gist.


Dear reader, if you've found this page you're probably encountering the error from the post title. Let me start by saying this post does not offer you a quick fix. Instead, it serves to show you the instance where I ran into this issue in Xcode 14, and why I believe this issue is a bug and not an actual issue. I've last tested this with Xcode 14.0's Release Candidate. I've filed feedback with Apple, the feedback number is FB11278036 in case you want to duplicate my issue.

Some of the SwiftUI code that I've been using fine for a long time now has recently started coming up with this purple warning.

Screenshot of "Publishing changes from within view updates is not allowed, this will cause undefined behavior." purple warning

Initially I thought that there was a chance that I was, in fact, doing something weird all along and I started chipping away at my project until I had something that was small enough to only cover a few lines, but still complex enough to represent the real world.

In this post I've collected some example of where I and other encounter this issue, along with whether it's been fixed or not.

[Fixed] Purple warnings when updating an @Published var from a Button in a List.

In my case, the issue happened with the following code:

class SampleObject: ObservableObject {
    @Published var publishedProp = 1337

    func mutate() {
        publishedProp = Int.random(in: 0...50)
    }
}

struct CellView: View {
    @ObservedObject var dataSource: SampleObject

    var body: some View {
        VStack {
            Button(action: {
                dataSource.mutate()
            }, label: {
                Text("Update property")
            })

            Text("\(dataSource.publishedProp)")
        }
    }
}

struct ContentView: View {
    @StateObject var dataSource = SampleObject()

    var body: some View {
        List {
            CellView(dataSource: dataSource)
        }
    }
}

This code really does nothing outrageous or weird. A tap on a button will simply mutate an @Published property, and I expect the list to update. Nothing fancy. However, this code still throws up the purple warning. Compiling this same project in Xcode 13.4.1 works fine, and older Xcode 14 betas also don't complain.

At this point, it seems like this might be a bug in List specifically because changing the list to a VStack or LazyVStack in a ScrollView does not give me the same warning. This tells me that there is nothing fundamentally wrong with the setup above.

Another thing that seems to work around this warning is to change the type of button that triggers the action. For example, using a bordered button as shown below also runs without the warning:

Button(action: {
    dataSource.mutate()
}, label: {
    Text("Update property")
}).buttonStyle(.bordered)

Or if you want your button to look like the default button style on iOS, you can use borderless:

Button(action: {
    dataSource.mutate()
}, label: {
    Text("Update property")
}).buttonStyle(.borderless)

It kind of looks like anything except a default Button in a List is fine.

For those reasons, I sadly cannot give you a proper fix for this issue. The things I mentioned are all workarounds IMO because the original code should work. All I can say is please file a feedback ticket with Apple so we can hopefully get this fixed, documented, or otherwise explained. I'll be requesting a code level support ticket from Apple to see if an Apple engineer can help me figure this out.

Animating a map's position in SwiftUI

A Map in SwiftUI is presented using the following code:

struct ContentView: View {
    @State var currentMapRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 10.0, longitude: 0.0), span: MKCoordinateSpan(latitudeDelta: 100, longitudeDelta: 100))

    var body: some View {
        VStack {
            Map(coordinateRegion: $currentMapRegion, annotationItems: allFriends) { friend in
                MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0)) {
                    Circle()
                        .frame(width: 20, height: 20)
                        .foregroundColor(.red)
                }
            }
        }
        .ignoresSafeArea()
    }
}

Notice how the Map takes a Binding for its coordinateRegion. This means that whenever the map changes what we're looking at, our @State can update and the other way around. We can assign a new MKCoordinateRegion to our @State property and the Map will update to show the new location. It does this without animating the change. So let's say we do want to animate to a new position. For example, by doing the following:

var body: some View {
    VStack {
        Map(coordinateRegion: $currentMapRegion, annotationItems: allFriends) { friend in
            MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: friend.cityLatitude ?? 0, longitude: friend.cityLongitude ?? 0)) {
                Circle()
                    .frame(width: 20, height: 20)
                    .foregroundColor(.red)
            }
        }
    }
    .ignoresSafeArea()
    .onAppear {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            withAnimation {
                currentMapRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 80, longitude: 80),
                                                      span: MKCoordinateSpan(latitudeDelta: 100, longitudeDelta: 100))
            }
        }
    }
}

This code applies some delay and then eventually moves the map to a new position. The animation could also be triggered by a Button or really anything else; how we trigger the animation isn't the point.

When the animation runs, we see lots and lots of warnings in the console (187 for me...) and they all say [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior..

We're clearly just updating our currentMapRegion just once, and putting print statements in the onAppear tells us that the onAppear and the withAnimation block are all called exactly once.

I suspected that the Map itself was updating its binding to animate from one position to the next so I changed the Map setup code a little:

Map(coordinateRegion: Binding(get: {
    self.currentMapRegion
}, set: { newValue, _ in
    print("\(Date()) assigning new value \(newValue)")
    self.currentMapRegion = newValue
}), annotationItems: allFriends) { friend in
    MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: friend.cityLatitude ?? 0, longitude: friend.cityLongitude ?? 0)) {
        Circle()
            .frame(width: 20, height: 20)
            .foregroundColor(.red)
    }
}

Instead of directly binding to the currentMapRegion property, I made a custom instance of Binding that allows me to intercept any write operations to see how many occur and why. Running the code with this in place, yields an interesting result:

2022-10-26 08:38:39 +0000 assigning new value MKCoordinateRegion(center: __C.CLLocationCoordinate2D(latitude: 62.973218679210305, longitude: 79.83448028564462), span: __C.MKCoordinateSpan(latitudeDelta: 89.49072082474844, longitudeDelta: 89.0964063502501))
2022-10-26 10:38:39.169480+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 10:38:39.169692+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 10:38:39.169874+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 08:38:39 +0000 assigning new value MKCoordinateRegion(center: __C.CLLocationCoordinate2D(latitude: 63.02444217894995, longitude: 79.96021270751967), span: __C.MKCoordinateSpan(latitudeDelta: 89.39019889305074, longitudeDelta: 89.09640635025013))
2022-10-26 10:38:39.186402+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 10:38:39.186603+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 10:38:39.186785+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 08:38:39 +0000 assigning new value MKCoordinateRegion(center: __C.CLLocationCoordinate2D(latitude: 63.04063284402105, longitude: 80.00000000000011), span: __C.MKCoordinateSpan(latitudeDelta: 89.35838016069978, longitudeDelta: 89.0964063502501))
2022-10-26 10:38:39.200000+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 10:38:39.200369+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
2022-10-26 10:38:39.200681+0200 MapBug[10097:899178] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.

This is just a small part of the output of course but we can clearly see that the print from the custom Binding is executed in between warnings.

I can only conclude that this has to be some issue in Map that we cannot solve ourselves. You might be able to tweak the custom binding a bunch to throttle how often it actually updates the underlying @State but I'm not sure that's what we should want...

If you're seeing this issue too, you can reference FB11720091 in feedback that you file with Apple.

Huge thanks to Tim Isenman for sending me this sample.

What are primary associated types in Swift 5.7?

Swift 5.7 introduces many new features that involve generics and protocols. In this post, we're going to explore an extremely powerful new features that's called "primary associated types". By the end of this post you will know and understand what primary associated types are, and why I think they are extremely important and powerful to help you write better code.

If your familiar with Swift 5.6 or earlier, you might know that protocols with associated types have always been somewhat of an interesting beast. They were hard to use sometimes, and before Swift 5.1 we would always have to resort to using generics whenever we wanted to make use of a protocol with an associated type. Consider the following example:

class MusicPlayer {
  func play(_ playlist: Collection) { /* ... */ } 
}

This example doesn't compile in Swift 5.1, and it still wouldn’t today in Swift 5.7. The reason is that Collection has various associated types that the compiler must be able to fill in if we want to use Collection. For example, we need to what kind of Element our collection holds.

A common workaround to use protocols with associated types in our code is to use a generic that's constrained to a protocol:

class MusicPlayer<Playlist: Collection> {
  func play(_ playlist: Playlist) { /* ... */ } 
}

If you're not quite sure what this example does, take a look at this post I wrote to learn more about using generics and associated types.

Instead of using Collection as an existential (a box that holds an object that conforms to Collection) we use Collection as a constraint on a generic type that we called Playlist. This means that the compiler will always know which object is used to fill in Playlist.

In Swift 5.1, the some keyword was introduced which, combined with Swift 5.7's capability to use the some keyword on function arguments, allows us to write the following:

class MusicPlayer {
  func play(_ playlist: some Collection) { /* ... */ } 
}

To learn more about the some keyword, I recommend you take a look at this post that explains everything you need to know about some.

This is nice, but both the generic solution and the some solution have an important issue. We don’t know what’s inside of the Collection. Could be String, could be Track, could be Album, there’s no way to know. This makes func play(_ playlist: some Collection) practically useless for our MusicPlayer.

In Swift 5.7, protocols can specify primary associated types. These associated types are a lot like generics. They allow developers to specify the type for a given associated type as a generic constraint.

For Collection, the Swift library added a primary associated type for the Element associated type.

This means that you can specify the element that must be in a Collection when you pass it to a function like our func play(_ playlist: some Collection). Before I show you how, let’s take a look at how a protocol defines a primary associated type:

public protocol Collection<Element> : Sequence {

  associatedtype Element
  associatedtype Iterator = IndexingIterator<Self>
  associatedtype SubSequence : Collection = Slice<Self> where Self.Element == Self.SubSequence.Element, Self.SubSequence == Self.SubSequence.SubSequence

  // a lot of other stuff
}

Notice how the protocol has multiple associated types but only Element is written between <> on the Collection protocol. That’s because Element is a primary associated type. When working with a collection, we often don’t care what kind of Iterator it makes. We just want to know what’s inside of the Collection!

So to specialize our playlist, we can write the following code:

class MusicPlayer {
  func play(_ playlist: some Collection<Track>) { /* ... */ }
}

Note that the above is functionally equivalent to the following if Playlist is only used in one place:

class MusicPlayer {
  func play<Playlist: Collection<Track>>(_ playlist: Playlist) { /* ... */ }
}

While the two snippets above are equivalent in functionallity the former option that uses some is preferred. The reason for this is that code with some is easier to read and reason about than having a generic that doesn't need to be a generic.

Note that this also works with the any keyword. For example, if we want to store our playlist on our MusicPlayer, we could write the following code:

class MusicPlayer {
    var playlist: any Collection<Track> = []

    func play(_ playlist: some Collection<Track>) {
        self.playlist = playlist
    }
}

With primary associated types we can write much more expressive and powerful code, and I’m very happy to see this addition to the Swift language.

Differences between Swift’s any and some keywords explained

Protocols are an extremely important part in the Swift language, and in recent updates we've received some new capabilities around protocol and generics that allow us to be much more intentional about how we use protocols in our code. This is done through the any and some keywords.

In this post, you will learn everything you need to know about the similarities and differences between these two keywords. We'll start with an introduction of each keyword, and then you'll learn a bit more about the problems each keyword solves, and how you can decide whether you should use some or any in your code.

The some keyword

In Swift 5.1 Apple introduced the some keyword. This keyword was key in making SwiftUI work because the View protocol defines an associated type which means that the View protocol couldn't be used as a type.

The following code shows how the View protocol is defined. As you'll notice, there's an associated type Body:

protocol View {
  associatedtype Body: View
  @ViewBuilder @MainActor var body: Self.Body { get }
}

If you’d try to write var body: View instead of var body: some View you’d see the following compiler error in Swift 5.7:

Use of protocol 'View' as a type must be written 'any View’

Or in older versions of Swift you’d see the following:

protocol can only be used as a generic constraint because it has Self or associated type requirements

The some keyword fixes this by hiding the concrete associated type from whoever interacts with the object that has some Protocol as its type. More on this later.

For a full overview of the some keyword, please refer to this post.

The any keyword

In Swift 5.6, the any keyword was added to the Swift language.

While it sounds like the any keyword acts as a type erasing helper, all it really does is inform the compiler that you opt-in to using an existential (a box type that conforms to a protocol) as your type.

Code that you would originally write as:

func getObject() -> SomeProtocol {
  /* ... */
}

Should be written as follows in Swift 5.6 and above:

func getObject() -> any SomeProtocol {
  /* ... */
}

This makes it explicit that the type you return from getObject is an existential (a box type) rather than a concrete object that was resolved at compile time. Note that using any is not mandatory yet, but you should start using it. Swift 6.0 will enforce any on existentials like the one that's used in the example you just saw.

Since both any and some are applied to protocols, I want to put them side by side in this blog post to better explain the problems they solve, and how you should decide whether you should use any, some, or something else.

For a full overview of the any keyword, please refer to this post.

Verify your existential usage for Swift 6 with Xcode 15.3

If you want to make sure that your app is ready for Swift 6.0 and uses any or some everywhere you're supposed to, pass the -enable-upcoming-feature ExistentialAny in your Swift build flags. To learn how, take a look at this post where I dig into experimental Swift versions and features. Note that the EsistentialAny build flag is available in the default Xcode 15.3 toolchain.

Understanding the problems that any and some solve

To explain the problems solved by any we should look at a somewhat unified example that will allow us to cover both keywords in a way that makes sense. Imagine the following protocol that models a Pizza:

protocol Pizza {
    var size: Int { get }
    var name: String { get }
}

It’s a simple protocol but it’s all we need. In Swift 5.6 you might have written the following function to receive a Pizza:

func receivePizza(_ pizza: Pizza) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

When this function is called, the receivePizza function receives a so-called box type for Pizza. In order to access the pizza name, Swift has to open up that box, grab the concrete object that implements the Pizza protocol, and then access name. This means that there are virtually no compile time optimizations on Pizza, making the receivePizza method more expensive than we’d like.

Furthermore, the following function looks pretty much the same, right?

func receivePizza<T: Pizza>(_ pizza: T) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

There’s a major difference here though. The Pizza protocol isn’t used as a type here. It’s used as a constraint for T. The compiler will be able to resolve the type of T at compile time and receivePizza will receive a concrete instance of a type rather than a box type.

Because this difference isn’t always clear, the Swift team has introduced the any keyword. This keyword does not add any new functionality. Instead, it forces us to clearly communicate “this is an existential”:

func receivePizza(_ pizza: any Pizza) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

The example that uses a generic <T: Pizza> does not need the any keyword because Pizza is used as a constraint and not as an existential.

Now that we have a clearer picture regarding any, let’s take a closer look at some.

In Swift, many developers have tried to write code like this:

let someCollection: Collection

Only to be faced by a compiler error to tell them that Collection has a Self or associated type requirement. In Swift 5.1 we can write some Collection to tell the compiler that anybody that accesses someCollection should not concern themselves with the specifics of the associated type and/or the Self requirement. They should just know that this thing conforms to Collection and that’s all. There's no information about the associated type, and the information about Self is not made available.

This mechanism is essential to making SwiftUI’s View protocol work.

The downside of course is that anybody that works with a some Collection, some Publisher, or some View can’t access any of the generic specializations. That problem is solved by primary associated types which you can read more about right here.

However, not all protocols have associated type requirements. For example, our Pizza protocol does not have an associated type requirement but it can benefit from some in certain cases.

Consider this receivePizza version again:

func receivePizza<T: Pizza>(_ pizza: T) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

We defined a generic T to allow the compiler to optimize for a given concrete type of Pizza. The some keyword also allows the compiler to know at compile time what the underlying type for the some object will be; it just hides this from the user of the object. This is exactly what <T: Pizza> also does. We can only access on T what is exposed by Pizza. This means that we can rewrite receivePizza<T: Pizza>(_:) as follows:

func receivePizza(_ pizza: some Pizza) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

We don’t need T anywhere else, so we don’t need to “create” a type to hold our pizza. We can just say “this function takes some Pizza" instead of “this function takes some Pizza that we’ll call T". Small difference, but much easier to write. And functionally equivalent.

Choosing between any and some

Once you understand the use cases for any and some, you’ll realize that it’s not a matter of choosing one over the other. They each solve their own very similar problems and there’s always a more correct choice.

Generally speaking you should prefer using some or generics over any whenever you can. You often don’t want to use a box that conforms to a protocol; you want the object that conforms to the protocol.

Or sticking with our pizza analogy, any will hand the runtime a box that says Pizza and it will need to open the box to see which pizza is inside. With some or generics, the runtime will know exactly which pizza it just got, and it’ll know immediately what to do with it (toss if it’s Hawaii, keep if it’s pepperoni).

In lots of cases you’ll find that you actually didn’t mean to use any but can make some or a generic work, and according to the Swift team, we should always prefer not using any if we can.

Making the decision in practice

Let’s illustrate this with one more example that draws heavily from my explanation of primary associated types. You’ll want to read that first to fully understand this example:

class MusicPlayer {
    var playlist: any Collection<String> = []

    func play(_ playlist: some Collection<String>) {
        self.playlist = playlist
    }
}

In this code, I use some Collection<String> instead of writing func play<T: Collection<String>>(_ playlist: T) because the generic is only used in one place.

My var playlist is an any Collection<String> and not a some Collection<String> for two reasons:

  1. There would be no way to ensure that the concrete collection that the compiler will deduce for the play method matches the concrete collection that’s deduced for var playlist; this means they might not be the same which would be a problem.
  2. The compiler can’t deduce what var playlist: some Collection<String> in the first place (try it, you’ll get a compiler error)

We could avoid any and write the following MusicPlayer:

class MusicPlayer<T: Collection<String>> {
    var playlist: T = []

    func play(_ playlist: T) {
        self.playlist = playlist
    }
}

But this will force us to always use the same type of collection for T. We could use a Set, an Array, or another Collection but we can never assign a Set to playlist if T was inferred to be an Array. With the implementation as it was before, we can:

class MusicPlayer {
    var playlist: any Collection<String> = []

    func play(_ playlist: some Collection<String>) {
        self.playlist = playlist
    }
}

By using any Collection<String> here we can start out with an Array but pass a Set to play, it’s all good as long as the passed object is a Collection with String elements.

In Summary

While some and any sound very complex (and they honestly are), they are also very powerful and important parts of Swift 5.7. It’s worth trying to understand them both because you’ll gain a much better understanding about how Swift deals with generics and protocols. Mastering these topics will really take your coding to the next level.

For now, know that some or generics should be preferred over any if it makes sense. The any keyword should only be used when you really want to use that existential or box type where you’ll need to peek into the box at runtime to see what’s inside so you can call methods and access properties on it.

Presenting a partially visible bottom sheet in SwiftUI on iOS 16

This post is up to date for Xcode 15 and newer. It supersedes a version of this post that you can find here

On iOS 15, Apple granted developers the ability to present partially visible bottom sheets using a component called UISheetPresentationController. Originally, we had to resort to using a UIHostingController to bring this component to SwiftUI.

With iOS 16, we don't have to do this anymore. You can make use of the presentationDetents view modifier to configure your sheets to be fully visible, approximately half visible, or some custom fraction of the screen's height.

To do this, you can apply the presentationDetents modifier by applying it to your sheet's content:

struct DetentsView: View {
    @State var isShowingSheet = false

    var body: some View {
        Button("Show the sheet!") {
            isShowingSheet = true
        }
        .sheet(isPresented: $isShowingSheet) {
            ZStack {
                Color(red: 0.95, green: 0.9, blue: 1)
                Text("This is my sheet. It could be a whole view, or just a text.")
            }
            .presentationDetents([.medium, .fraction(0.7)])
        }
    }
}

Here's what the sheet looks like when it's presented on an iPhone:

An example of a bottom sheet on iPhone

In this example, my sheet will initially take up about half the screen and can be expanded to 70% of the screen height. If I want to allow the user to expand my sheet to the full height of the screen I would add the .large option to the list of presentationDetents.

By default, sheets will only support the .large detent so you don't need to use the presentationDetents view modifier when you want your sheet to only support the view's full height.

Formatting dates in Swift using Date.FormatStyle on iOS 15

Working with dates isn’t easy. And showing them to your users in the correct locale hasn’t always been easy either. With iOS 15, Apple introduced a new way to convert Date objects from and to String. This new way comes in the form of the new Formatter api that replaces DateFormatter.

As any seasoned iOS developer will tell you, DateFormatter objects are expensive to create, and therefor kind of tedious to manage correctly. With the new Formatter api, we no longer need to work with DateFormatter. Instead, we can ask a date to format itself based on our requirements in a more performant, easier to use way.

In this post I will show you how you can convert Date objects to String as well as how you can extract a Date from a String.

Converting a Date to a String

The most straightforward way to convert a Date to a String is the following:

let formatted = Date().formatted() // 5/26/2022, 7:52 PM

By default, the formatted() function uses a compact configuration for our String. The way formatted() converts our Date to String takes into account the user’s current locale. For example, if my device was set to be in Dutch, the date would be formatted as 26-5-2022 19:54 which is a more appropriate formatting for the Dutch language.

However, this might not always be what we need. For example, we might want to have our date formatted as May 26 2022, 7:52 PM. We can use the following code to do that:

let formatted = Date().formatted(
    .dateTime
        .day().month(.wide).year()
        .hour().minute()
)

Let’s break this code apart a bit. The formatted function takes an object that conforms to the FormatStyle protocol as its argument. There are various ways for us to create such an object. The FormatStyle protocol has several convenient extensions that can provide us with several different formatters.

For example, when sending a Date to a server, we’ll often need to send our dates as ISO8601 compliant strings. Before I explain the code you just saw, I want to show you how to grab an ISO8601 compliant string from the current Date.

let formatted = Date().formatted(.iso8601) // 2022-05-26T18:06:55Z

Neat, huh?

Okay, back to the example from before. The .datetime formatter is used as a basis for our custom formatting. We can call various functions on the object that’s returned by the .datetime static property to select the information that we want to show.

Some of these properties, like the month, can be configured to specify how they should be formatted. In the case of .month, we can choose the .wide formatting to spell out the full month name. We could use .narrow to abbreviate the month down to a single letter, or we could use one of the other options to represent the month in different ways.

If you omit a property, like for example .year(), our formatted date will omit the year that’s embedded in the Date. And again, the underlying formatter will always automatically respect your user’s locale which is really convenient.

Another way to format the date is to by specifying how you want the date and time to be formatted respectively:

let formatted = Date().formatted(date: .complete, time: .standard) // Thursday, May 26, 2022, 8:15:28 PM

The above provides a very verbose formatted string. We can make a more compact one using the following settings:

let formatted = Date().formatted(date: .abbreviated, time: .shortened) // May 26, 2022, 8:16 PM

It’s even possible to omit the date or time entirely by using the .omitted option:

let formatted = Date().formatted(date: .abbreviated, time: .omitted) // May 26, 2022

There are tons of different combinations you could come up with so I highly recommend you explore this api some more to get a sense of how flexible it really is.

Creating a Date from a String

Converting String to Date is slightly less convenient than going from a Date to a String but it’s still not too bad. Here’s how you could cover the common case of converting an ISO8601 compliant string to a Date:

let string = "2022-05-26T18:06:55Z"
let expectedFormat = Date.ISO8601FormatStyle()
let date = try! Date(string, strategy: expectedFormat)

We make use of the Date initializer that takes a string and a formatting strategy that’s used to parse the string.

We can also use and configure an instance of FormatStyle to specify the components that we expect to be present in our date string and let the system parse it using the user’s locale:

let string = "May 26, 2022, 8:30 PM"
let expectedFormat = Date.FormatStyle()
    .month().year().day()
    .hour().minute()
let date = try! Date(string, strategy: expectedFormat)

The order of our date components doesn’t matter; they will automatically be rearranged to match the user’s locale. This is super powerful, but it does mean that we can’t use this to parse dates on devices that use a different locale than the one that matches the string’s locale. The best locale agnostic date string is ISO8601 so if you have control over the date strings that you’ll parse, make sure you use ISO8601 when possible.

Summary

In this short article, you learned how you can use iOS 15’s FormatStyle to work format Date objects. You saw how to go from Date to String, and the other way around. While FormatStyle is more convenient than DateFormatter, it’s iOS 15 only. So if you’re still supporting iOS 14 you’ll want to make sure to check out DateFormatter too.