Swift 6.1, released on March 31, 2025, extends nonisolated so it can be applied to types and extensions. The feature looks small, but it gives concurrency migrations a better vocabulary. When a whole group of declarations is independent of an actor or global actor, the isolation intent can now appear at that boundary instead of being repeated on individual members.

The important word is intent. nonisolated is not a switch that disables concurrency safety.

What nonisolated promises

A nonisolated declaration is not bound to an actor executor. It may be called without hopping to that actor, but it still cannot freely read or mutate actor-isolated state. Values crossing concurrency boundaries still need to satisfy Sendable requirements, and shared mutable memory still needs a synchronization design.

An immutable request value is a good candidate:

nonisolated struct SearchQuery: Sendable, Equatable {
    let text: String
    let page: Int

    init(text: String, page: Int = 1) {
        self.text = text.trimmingCharacters(in: .whitespacesAndNewlines)
        self.page = max(1, page)
    }
}

SearchQuery contains sendable values, performs deterministic normalization, and has no UI dependency. Constructing or comparing it does not need the main actor. A type-level annotation communicates that fact more clearly than repeating an annotation on each initializer and computed property.

This does not mean every data model should be marked nonisolated. A reference type with mutable shared storage needs an ownership or synchronization story first. The annotation should follow that design, not substitute for it.

Separate UI isolation from value semantics

The UI-facing object can remain explicitly main-actor isolated:

@MainActor
final class SearchViewModel {
    private(set) var results: [String] = []
    private let client: SearchClient

    init(client: SearchClient) {
        self.client = client
    }

    func search(_ query: SearchQuery) async throws {
        let values = try await client.fetch(query)
        results = values
    }
}

The query may be created in any suitable concurrency context. The client owns the asynchronous I/O boundary. Only the mutation of UI state belongs to the main actor. Making helper values main-actor isolated as well would add executor hops without protecting anything meaningful.

Extension-level nonisolated serves a related purpose. If a type receives global-actor isolation from its surrounding context or project defaults, a collection of pure formatting or conversion methods can state together that they do not need that actor. Before applying it, inspect every stored property those methods touch. One actor-isolated dependency is enough to make a blanket annotation incorrect.

Classify a diagnostic before fixing it

A risky migration strategy is to search for concurrency errors and add nonisolated until the project compiles. The diagnostic is evidence that the isolation design is incomplete. Classify the declaration first:

  • UI state normally stays on @MainActor.
  • Mutable state with independent lifetime may belong in an actor.
  • Immutable data crossing tasks should usually be Sendable.
  • APIs genuinely unrelated to an executor may be nonisolated.
  • Legacy callbacks may need an adapter that makes the ownership transition explicit.

The same caution applies to @unchecked Sendable. It transfers the proof of thread safety from the compiler to the developer. If a reference type uses a lock, document which fields the lock protects, whether callbacks can re-enter the object, and how shutdown works. Without that proof, unchecked conformance only hides a race from static analysis.

For a large migration, enable stricter checking in one module and classify its diagnostics before editing several targets at once. Keep genuine isolation redesigns separate from third-party compatibility adapters in review. A temporary annotation should include the assumption that makes it safe and the condition under which it can be removed. Otherwise, an exception introduced during migration can outlive the library or compiler limitation that originally justified it.

Public APIs need extra care because their isolation becomes part of the contract. Moving a protocol requirement onto or off a global actor can affect every conformer. Prototype that change at the module boundary and compile representative clients before making a package release.

Test the runtime rules

The compiler catches many illegal isolation crossings, but it does not decide product behavior. Tests should still cover cancellation, overlapping searches, response ordering, and stale-result suppression. Run a query from a background task, cancel the request, start two requests in quick succession, and verify that an older response cannot overwrite a newer state.

These tests validate the boundaries suggested by the type system. They are more useful than testing that a particular method happened to run on a named thread, because Swift concurrency is expressed in actors and tasks rather than in a permanent thread assignment.

Type- and extension-level nonisolated reduce annotation repetition in Swift 6.1. Their larger benefit is conceptual: pure values, isolated UI state, and asynchronous services can state their execution requirements at the right level. Use the feature to clarify a concurrency design that you can explain, not to silence a diagnostic that you have not yet understood.