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

Pull request Support watchOS on Quick/Quick


This is a counterpart to https://github.com/Quick/Nimble/pull/916, which added support for watchOS to Nimble.

Note that this is a draft until a new version of Nimble is released with watchOS support. Until then this points at main.

I'm also not sure if the tests will run on CI correctly because the tests require watchOS 7.4 to run and I'm not sure what version of watchOS the simulators are configured with.

I also found that the tests weren't compiling (using Xcode 13.2.1) so I've updated these too.

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

Pull request Correctly calculate `CollectionFlowLayoutEnvironment.contentSize` on composed-swift/Composed


Here's a small PR for a bug that I think has just gone unnoticed because we've never used the sectionInset property of the UICollectionViewFlowLayout, and we've never used the contentInset property of UICollectionView.

The issues looks to be that the layout's sectionInset property is applied on a per-section basis, but the collection view's contentInset was not being honoured.

It was also calculated using insetBy(dx:dy:), which will modify the width by -dx * 2, which would double the expected insets.

Pull request Add support for watchOS on Quick/Nimble


This PR is to add support for running on watchOS. Since Xcode 12.5 watchOS has been supported but Nimble doesn't currently compile for watchOS.

A new target has been added to the project, along with explicit support in the Package.swift.

I don't believe anything here is a breaking change.

I have a fork of Quick that also supports watchOS. I'll create a PR for that if this PR gets approved and merged.

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

VaporDocC v0.1.0


Release Notes

  • Support redirecting requests for the root (“/“)
  • Support redirecting requests with missing trailing slashes to include trailing slashes

Pull request Add JosephDuffy/VaporDocC.git on SwiftPackageIndex/PackageList


The package(s) being submitted are:

Checklist

I have either:

  • [x] Run swift ./validate.swift.

Or, checked that:

  • [ ] The package repositories are publicly accessible.
  • [ ] The packages all contain a Package.swift file in the root folder.
  • [ ] The packages are written in Swift 5.0 or later.
  • [ ] The packages all contain at least one product (either library or executable), and at least one product is usable in other Swift apps.
  • [ ] The packages all have at least one release tagged as a semantic version.
  • [ ] The packages all output valid JSON from swift package dump-package with the latest Swift toolchain.
  • [ ] The package URLs are all fully specified including the protocol (usually https) and the .git extension.
  • [ ] The packages all compile without errors.
  • [ ] The package list JSON file is sorted alphabetically.

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

Pull request Cache `SingleElementSections.numberOfElements` on composed-swift/Composed


Overall this is pretty minor but when dealing with a lot of SingleElementSections that are infrequently updated is does start to slow down.

I thought a faster approach would be to use generics on replace(element:) to only check for nil when the value is Optional, but generics don't allow for this kind of overload.

Also added some basic tests. The performance tests show a ~9% performance increase.

Pull request Cache nib registrations to improve performance on composed-swift/Composed


@bill201207 found that UINib(nibName:bundle:) is a big part of the poor performance when our app launches.

Ultimately batching updates will help with a lot of things like this, but really that'll just be masking some of the performance issues so it's useful to find issues like this now.

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:)

Pull request Merge ComposedLayout and ComposedUI packages on composed-swift/Composed


This adds the ComposedLayout and ComposedUI packages, which will close #16.

The base for this is 2.0-beta, which we can use for testing new changes, which can also break API.

If you go to the repo settings you can (temporarily) set the default branch to 2.0-beta and we can add a note to the README stating this is the beta release and the stable release is available in the master branch.

Once merged and these changes are made the READMEs for ComposedLayout and ComposedUI repos can be updated to point to this repo and their repos archived.

Pull request Add `FlatSection` on composed-swift/Composed


This is a new type of section that is similar to ComposedSectionProvider, but rather than flattening each of the children in to a single SectionProvider it flattens them in to a single Section.

The ComposedUI side of this has been updated to support multiple cells per section, with a convenience for FlatSection that delegates the calls to each of the flattened sections.

This is a breaking change since the protocol requirements have changed. I have set the base of the PR to merge-all-libraries since that's also a breaking change and this PR relies on those changes.

edit: We've moved to our own fork to allow for more rapid development. The latest changes are in https://github.com/opennetltd/Composed/tree/feature/FlatSection, which will eventually be merged back in to this repo once some of the other PRs/issues have been resolved.

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