Skip to content

Entries with the swift tag

Smuggling Values Across Actors


I recently started updating an app to use the Swift 6 language mode and ran in to an issue using AVCaptureMetadataOutputObjectsDelegate. The issue is that the compiler cannot reason about which actor the delegate function is called on and it must be treated as nonisolated, however we know that it's being called on a specific actor (in this case the MainActor), so how can we tell the compiler?

In Swift 5 language mode we can use MainActor.assumeIsolated and call it a day. But in Swift 6 this will produce an error:

import AVFoundation
import UIKit

@MainActor
final class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    private var output: AVCaptureMetadataOutput?

    override func viewDidLoad() {
        super.viewDidLoad()

        let output = AVCaptureMetadataOutput()
        output.setMetadataObjectsDelegate(self, queue: .main)
        self.output = output
    }

    private func doSomethingWithMetadataObjects(_ metadataObjects: [AVMetadataObject]) {}

    // Must be marked nonisolated because the AVCaptureMetadataOutputObjectsDelegate protocol cannot declare the actor on which the function will be called.
    nonisolated func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        // We know it's on the main actor so we should be able to assume
        // isolated and pass metadata objects to another function.
        MainActor.assumeIsolated {
            // This does not compile though, presumably because AVMetadataObject
            // is not Sendable.
            doSomethingWithMetadataObjects(metadataObjects) // ❌ Error: Sending 'metadataObjects' risks causing data races
        }
    }
}

This reminded me of a macro I created recently, or more specifically the type used to workaround a similar issue with stored properties in Swift 5: UnsafeSendable. This type works by using @unchecked Sendable to send store values we know are sendable but have no native way of telling the compiler.

In this case, however, it can also be used to smuggle values across an actor boundary that we know is safe.

public struct Smuggler<Smuggled>: @unchecked Sendable {
    public var smuggled: Smuggled

    public init(unsafeSmuggled smuggled: Smuggled) {
        self.smuggled = smuggled
    }

    @available(*, deprecated, message: "Smuggler is not needed when `Smuggled` is Sendable")
    public init(unsafeSmuggled smuggled: Smuggled) where Smuggled: Sendable {
        self.smuggled = smuggled
    }
}

With this we can smuggle our value through:

    nonisolated func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        // We know it's on the main actor so we should be able to assume
        // isolated and pass metadata objects to another function.
        let smuggler = Smuggler(unsafeSmuggled: metadataObjects)
        MainActor.assumeIsolated {
            let metadataObjects = smuggler.smuggled
            doSomethingWithMetadataObjects(metadataObjects) // ✅ Works
        }
    }

We can add an extension to MainActor that makes this a little more convenient.

extension MainActor {
    public static func assumeIsolated<Smuggled>(
        smuggling smuggled: Smuggled,
        operation: @MainActor (_ smuggled: Smuggled) -> Void,
        file: StaticString = #fileID,
        line: UInt = #line
    ) {
        let smuggler = Smuggler(unsafeSmuggled: smuggled)
        withoutActuallyEscaping(operation) { escapingOperation in
            let smuggledOperation = Smuggler(unsafeSmuggled: escapingOperation)
            assumeIsolated({
                smuggledOperation.smuggled(smuggler.smuggled)
            }, file: file, line: line)
        }
    }
}

I have been playing around with trying to add a function to GlobalActor that would make the API a bit nicer but I think it's going to require a macro, lest we resort to reimplementing MainActor.assumeIsolated 😊

Although this isn't really "smuggling" because the value is not actually crossing an actor boundary I like the term and I'm sticking to it! Especially because I assume this workaround will be temporary.

P.S. I created a demo project of the issue, which I also submitted to Apple as feedback FB13950073 ✌️

Partial in Swift


Partial is now available in its own Swift package on GitHub. This post is still valid, but somewhat out of date.

Structs are incredibly useful in Swift, especially when representing static read-only data. However, the values of a struct often come from multiple sources, such as view controllers, network requests, and files on disk, which can make the creation of these structs cumbersome.

There are numerous methods to work around this, but each have their downsides. One of these methods is to change the struct to a class and update the properties to vars, but this removes the advantages of read-only structs. Another is to make a "builder" object, but the API of this object must be kept in-sync with the object is wraps.

Partial eliminates these problems by providing a type-safe API for building structs by utilising generics and KeyPaths. Although I learned of the concept of Partial through TypeScript – which [provides Partial as a built-in type][1] – the Swift implementation supports many more use cases.

Keep Reading →

Don't Use Scope Modifiers with Extensions


Extending types in Swift support setting the scope for the extension, i.e. public, internal, or private, with internal being implicit if nothing is specified.

This may seem useful, but given the following snippet it's impossible to know what the scope of a function is:

func doSomething() {
    // Do the thing
}
Keep Reading →

Swift Package Collection Signing Using the Terminal


Swift Packages are JSON files that describe a collection of packages. This post will explain how to sign these packages with a trusted certificate entirely from the terminal. These methods should work on Linux and macOS alike. At the end I describe how to have Swift on Linux implicitly trust these packages.

Using this technique I have published my own package collection.

If you're targeting macOS only and find GUIs more intuitive I recommend following the “Swift Package Collection” blog post from Shaps, which is the post that finally made this “click” for me.

Keep Reading →

Hosting DocC Archives


At WWDC21 Apple introduced DocC, a tool for creating archives of Swift documentation that includes the static files required to host a version of the documentation on a website.

In this post I will summarise various methods of serving a DocC archive:

  • Netlify
  • Vapor middleware
  • nginx
  • Apache

All the examples provided here are hosting the DocC archive for VaporDocC, the Vapor middleware I wrote for hosting DocC archives.

Keep Reading →

My WWDC 2021 Wishlist


With WWDC 2021 just around the corner I've been thinking about what I'd like to see there.

A lot of the popular discourse around this time of year is focussed on features of the operating systems but I want to look at what I'd like to see as a developer for Apple platforms.

I love to develop for Apple platforms but it can often be a painful process. May is like a christmas for Apple developers.

Keep Reading →

Mapping Optional Binding to Bool


When displaying an alert in SwiftUI, if the value used to calculate whether the alert is presented is both Optional and does not conform to Identifiable1 it is often recommended to use a separate flag, similar to:

struct ContentView: View {
    @State private var alertText: String?
    @State private var isPresentingAlert = false

    var body: some View {
        Button("Show Alert") {
            self.alertText = "Alert Text"
            self.isPresentingAlert = true
        }
        .alert(isPresented: $isPresentingAlert) {
            Alert(title: Text(alertText!))
        }
    }
}

There are 2 main downsides to this:

  1. alertText is not set back to nil, which may cause bugs and will increase memory usage (even if only a little in this case)
  2. The isPresentingAlert flag needs to be managed

To work around these issues I create a small extension to Binding the allows this same code to be updated to:

struct ContentView: View {
    @State private var alertText: String?

    var body: some View {
        Button("Show Alert") {
            self.alertText = "Alert Text"
        }
        .alert(isPresented: $alertText.mappedToBool()) {
            Alert(title: Text(alertText!))
        }
    }
}

The extension is fairly small and simple:

import os.log
import SwiftUI

extension Binding where Value == Bool {
    /// Creates a binding by mapping an optional value to a `Bool` that is
    /// `true` when the value is non-`nil` and `false` when the value is `nil`.
    ///
    /// When the value of the produced binding is set to `false` the value
    /// of `bindingToOptional`'s `wrappedValue` is set to `nil`.
    ///
    /// Setting the value of the produce binding to `true` does nothing and
    /// will log an error.
    ///
    /// - parameter bindingToOptional: A `Binding` to an optional value, used to calculate the `wrappedValue`.
    public init<Wrapped>(mappedTo bindingToOptional: Binding<Wrapped?>) {
        self.init(
            get: { bindingToOptional.wrappedValue != nil },
            set: { newValue in
                if !newValue {
                    bindingToOptional.wrappedValue = nil
                } else {
                    os_log(
                        .error,
                        "Optional binding mapped to optional has been set to `true`, which will have no effect. Current value: %@",
                        String(describing: bindingToOptional.wrappedValue)
                    )
                }
            }
        )
    }
}

extension Binding {
    /// Returns a binding by mapping this binding's value to a `Bool` that is
    /// `true` when the value is non-`nil` and `false` when the value is `nil`.
    ///
    /// When the value of the produced binding is set to `false` this binding's value
    /// is set to `nil`.
    public func mappedToBool<Wrapped>() -> Binding<Bool> where Value == Wrapped? {
        return Binding<Bool>(mappedTo: self)
    }
}

The extension isn't tied directly to showing an alert or a sheet and can be used in any context, but this is one of the better examples of its usage.

This extension is available on GitHub under the MIT license.

1 If it does conform to Identifiable use alert(item:content:)

Supporting Multiple Swift Package Versions Without Breaking Compatibility


The Xcode 12 beta includes Swift 5.3 but drops support for iOS 8.x. This means that Swift packages that support iOS 8 will cause a warning:

The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.

It's not possible to remove this warning within a project that depends on a Swift package with a deployment target of iOS 8, but it is possible to fix this in the dependency without removing support for iOS 8 for older versions of Swift. There are multiple way this can be accomplished.

Keep Reading →

My Swift Package Manager Release Workflow


I am currently maintaining numerous Swift Packages that don't receive a constant flow of updates, but do receive updates when new Swift updates come out, or as I think of useful additions.

To ensure that I can make some of these less frequent updates without too much friction and with confidence in their correctness I rely heavily on GitHub Actions, which I'll go over in this blog post.

Keep Reading →

Capturing More Than `self`


A common pattern when using closures in Swift is to add [weak self] in the captures list to hold a weak reference to self and avoid a retain cycle. This is then often followed by the following:

guard let self = self else { return }

But I often forget that capture lists can capture other variables in the current scope, so I thought I'd highlight some other use cases.

Keep Reading →

HashableByKeyPath framework release 1.0.0


Today I have released the 1.0.0 version of a Swift package that aids with adding Equatable and Hashable conformance by using KeyPaths.

The package is available on GitHub.

I created the Swift Playground that sparked this concept in December 2018, so this concept has been rattling around in my brain for a couple of years. The API has changed a lot since the original concept, but the core has stayed the same: a protocol that requires a single function to be implemented that uses KeyPaths to synthesise Equatable and/or Hashable conformance.

Keep Reading →

Partial framework release 1.0.0


Today marks 1 year since I released a blog post demonstrating an implementation of Partial in Swift, and it also marks the release of the 1.0.0 version of a Swift package for Partial.

The package is available on GitHub and supports SwiftPM, Carthage, and CocoaPods.

This blog post will go over some of the changes that have been made since the original blog post, my rationale when making certain decisions, and how I have thought about maintenance. If you want to try out Partial and see how it can be used head over to the GitHub page.

Keep Reading →