Whenever your type conforms to a certain actor isolation domain, you’ll notice it becomes difficult to conform to nonisolated protocol conformances. Up until Swift 6.2, there was no alternative to keeping your types nonisolated. Adding conformance to common protocols like Equatable suddenly became troublesome.

Luckily enough, SE-470 introduced global-actor isolated conformance. This allows you to add conformance to a given protocol, given that any of the protocol members are called on the actor’s isolation domain.

Understanding why we need global-actor isolated conformance

Before we dive into the details of this new feature, I want you to understand what we’re solving. Imagine the following view model:

@MainActor
final class PersonViewModel {
    let id: UUID
    var name: String

    init(id: UUID, name: String) {
        self.id = id
        self.name = name
    }
}

Since we’re using the view model with UI only, we marked it with the @MainActor. We decide to add Equatable conformance with a custom comparison method to only compare on the person’s id:

A typical example of something you would expect just to work suddenly results in a compiler issue. In this particular case, we can solve it by marking the method explicitly as nonisolated:

extension PersonViewModel: Equatable {
    nonisolated static func == (lhs: PersonViewModel, rhs: PersonViewModel) -> Bool {
        lhs.id == rhs.id
    }
}

The id property is immutable and Sendable, allowing it to cross isolation domains for comparison. This changes when we later decide to involve the name property in the equation as well:

Since name is mutable, we suddenly risk data races due to different isolation domains. The Equatable method could be reading name while it’s being mutated, causing an exception. This is what Strict Concurrency prevents with this compiler issue.

Up until Swift 6.2, we could solve this by assuming main actor isolation:

extension PersonViewModel: Equatable {
    nonisolated static func == (lhs: PersonViewModel, rhs: PersonViewModel) -> Bool {
        MainActor.assumeIsolated {
            lhs.id == rhs.id && lhs.name == rhs.name
        }
    }
}

This method’s documentation reads as follows:

Assume that the current task is executing on the main actor’s serial executor, or stop program execution.

In other words, we take on the responsibility to ensure it’s always called on the main actor, but nothing prevents us from breaking that assumption. You essentially step away from compile-time safety, which is also why I didn’t introduce the assumeIsolated method earlier in this course—I don’t want you to use it.

Using global actor isolation conformance

Swift 6.2 introduces a new upcoming feature:

You can enable the same feature inside packages by using InferIsolatedConformances:

.target(
    name: "YourPackageTarget",
    swiftSettings: [
        .enableUpcomingFeature("InferIsolatedConformances")
    ]
)

Once enabled, we can add conformance as follows:

/// Upcoming feature `InferIsolatedConformances` allows us to write `: @MainActor Equatable`:
extension PersonViewModel: @MainActor Equatable {
    static func == (lhs: PersonViewModel, rhs: PersonViewModel) -> Bool {
        lhs.id == rhs.id && lhs.name == rhs.name
    }
}

This is effectively saying that PersonViewModel will only ever be considered Equatable on the main actor. Violating this assumption will result in a compile-time error detected when == is called from outside the main actor:

Summary

Global actor isolated conformances solve a missing piece for Swift Concurrency. Before this feature, migrating to Strict Concurrency truly became difficult. It’s amazing to see the Swift Compiler being smart enough to detect protocol conformance isolation domains.

In the next lesson, we’re going to look into actor reentrancy.