01 · INTRODUCTION
What Does nonisolated Mean in Swift?
The short answer
nonisolated marks a declaration as not isolated to an actor. Code can use that declaration without first entering the actor, but the declaration cannot directly access the actor's protected isolated state.
CODE EXAMPLE
actor LaunchCatalog {
nonisolated let sourceName: String
private var launches: [Launch] = []
init(sourceName: String) {
self.sourceName = sourceName
}
nonisolated func makeLogPrefix() -> String {
"[LaunchCatalog: \(sourceName)]"
}
func store(_ launch: Launch) {
launches.append(launch)
}
}
makeLogPrefix() uses only the catalogue's nonisolated, immutable identity. A caller can invoke it synchronously. By contrast, store(_:) accesses protected mutable state and remains actor-isolated.
The central rule
nonisolated removes an actor-isolation requirement. It does not mean “execute in the background.”
02 · TUTORIAL
Actor Members Are Isolated by Default
Instance properties and methods declared on an actor are normally isolated to that actor:
CODE EXAMPLE
actor LaunchCatalog {
private var launches: [Launch] = []
func count() -> Int {
launches.count
}
}
Code already running on the actor can call count() synchronously. Code outside the actor crosses into the actor's isolation domain and therefore uses await:
CODE EXAMPLE
let catalog = LaunchCatalog()
let count = await catalog.count()
The potential suspension gives the actor an opportunity to execute the request when it can safely provide isolated access.
This default is appropriate for operations involving the actor's protected state. It can be unnecessary for a member whose result depends only on immutable, safely shared information.
03 · TUTORIAL
nonisolated Opts a Member Out
Adding nonisolated means the declaration has no isolated self:
CODE EXAMPLE
actor LaunchCatalog {
nonisolated let sourceName: String
init(sourceName: String) {
self.sourceName = sourceName
}
nonisolated func sourceLabel() -> String {
sourceName.uppercased()
}
}
let catalog = LaunchCatalog(sourceName: "Orbital API")
let label = catalog.sourceLabel()
The final call needs no await. The method is callable like an ordinary synchronous method because it does not require access to the actor's executor.
Important Terminology
An actor-isolated member requires access to a particular actor's isolation domain. A nonisolated member does not.
04 · TUTORIAL
A nonisolated Member Cannot Read Mutable Actor State
The compiler enforces the boundary:
CODE EXAMPLE
actor LaunchCatalog {
nonisolated let sourceName: String
private var launches: [Launch] = []
nonisolated func summary() -> String {
// Error: actor-isolated property 'launches'
// cannot be referenced from a nonisolated context.
"\(sourceName): \(launches.count) launches"
}
}
If this were allowed, callers from several concurrency domains could synchronously reach launches without the actor serializing access. That would defeat the reason the state belongs to an actor.
Removing the reference to isolated state makes the declaration valid:
CODE EXAMPLE
nonisolated func summary() -> String {
"Launch source: \(sourceName)"
}
The compiler error is useful design feedback. If the result needs the current catalogue count, the operation really does depend on isolated state and should usually remain actor-isolated.
05 · TUTORIAL
What Data Can a nonisolated Member Use?
Use nonisolated for immutable identity
A common use is stable identity established during initialization:
CODE EXAMPLE
actor LaunchService {
nonisolated let serviceID: UUID
nonisolated let providerName: String
private var cachedLaunches: [Launch] = []
init(
serviceID: UUID = UUID(),
providerName: String
) {
self.serviceID = serviceID
self.providerName = providerName
}
nonisolated var diagnosticName: String {
"\(providerName)[\(serviceID)]"
}
func replaceCache(with launches: [Launch]) {
cachedLaunches = launches
}
}
UUID and String are Sendable value types. The nonisolated stored properties are immutable, so they can be read safely without entering the actor.
The mutable cache remains isolated. Making identity easy to read does not expose the actor's changing state.
Why the value must be safe to share
A nonisolated declaration may be used from different isolation domains. Its parameter and result values therefore need to satisfy Swift's safety rules for crossing those boundaries.
CODE EXAMPLE
final class MutableMetadata {
var name: String = "Orbital API"
}
actor LaunchCatalog {
// Invalid design: a synchronously accessible reference
// could expose shared mutable state outside the actor.
nonisolated let metadata: MutableMetadata
}
MutableMetadata is a mutable class that does not conform safely to Sendable. Exposing it as nonisolated would allow callers to share and mutate the same reference outside the actor's protection.
Prefer immutable Sendable values for nonisolated actor identity:
CODE EXAMPLE
struct LaunchProviderMetadata: Sendable {
let name: String
let region: String
}
actor LaunchCatalog {
nonisolated let metadata: LaunchProviderMetadata
}
06 · TUTORIAL
nonisolated Is Useful for Synchronous Protocol Requirements
Many existing protocols contain synchronous requirements. CustomStringConvertible, for example, expects a synchronous description property:
CODE EXAMPLE
protocol CustomStringConvertible {
var description: String { get }
}
An actor-isolated property cannot normally satisfy a nonisolated synchronous requirement, because protocol callers would have no way to write await. A nonisolated implementation can:
CODE EXAMPLE
actor LaunchCatalog {
nonisolated let sourceName: String
private var launches: [Launch] = []
init(sourceName: String) {
self.sourceName = sourceName
}
}
extension LaunchCatalog: CustomStringConvertible {
nonisolated var description: String {
"Launch catalogue provided by \(sourceName)"
}
}
The conformance is sound because description uses only nonisolated data. It cannot include the current number of cached launches without becoming actor-isolated and no longer satisfying this synchronous requirement.
07 · TUTORIAL
Do Not Add nonisolated Merely to Remove await
await is not clutter to eliminate. It communicates a possible suspension and a crossing into isolated state.
CODE EXAMPLE
actor LaunchCatalog {
private var launches: [Launch] = []
func snapshot() -> [Launch] {
launches
}
}
snapshot() must read the protected cache. Keeping it isolated is correct:
CODE EXAMPLE
let launches = await catalog.snapshot()
You cannot make the method nonisolated without either receiving a compiler error or moving the state outside the actor's protection. The caller's await accurately describes the design.
Choose nonisolated from data ownership, not call-site convenience.
If a member needs protected actor state, it should remain isolated.
08 · TUTORIAL
nonisolated Does Not Mean Background Execution
Isolation and thread placement are separate concepts.
CODE EXAMPLE
nonisolated func sourceLabel() -> String {
sourceName.uppercased()
}
This synchronous method runs as part of the caller's ordinary execution. It does not create a task, dispatch work, select a global executor or move work to a background thread.
Declaration
nonisolated
What it says
This declaration is not isolated to the surrounding actor
Declaration
async
What it says
This function can contain suspension points
Declaration
@concurrent
What it says
This async function explicitly switches off the caller's actor to run
Declaration
Task { }
What it says
This expression creates an unstructured task
These features answer different questions. Marking a function nonisolated is not a performance strategy.
09 · TUTORIAL
A nonisolated Function Can Still Be async
A function can be both nonisolated and asynchronous:
CODE EXAMPLE
actor LaunchCatalog {
nonisolated func validate(
_ manifest: LaunchManifest
) async -> ValidationResult {
await manifestValidator.validate(manifest)
}
}
The caller still writes await because the function is async, not because it must enter LaunchCatalog. Inside the method, direct access to the catalogue's isolated mutable state remains prohibited.
There is one modern Swift detail worth knowing. Swift 6.2 introduced caller-isolated execution for nonisolated async functions through nonisolated(nonsending) and the NonisolatedNonsendingByDefault language mode:
CODE EXAMPLE
nonisolated(nonsending)
func validateLocally(_ manifest: LaunchManifest) async -> Bool {
manifest.isStructurallyValid
}
This form runs on the caller's actor when one exists. In language modes using the newer default, plain nonisolated async functions receive that behaviour automatically. In older modes, their default execution semantics differ.
If an async function is deliberately meant to switch off an actor and run concurrently with that actor, modern Swift expresses that separate intent with @concurrent. Neither spelling grants access to the surrounding actor's isolated state.
The stable lesson is simple: nonisolated describes static access to actor state. Do not infer a thread or performance outcome from that word alone.
10 · TUTORIAL
nonisolated on MainActor Types
The same principle applies to types isolated by a global actor:
CODE EXAMPLE
@MainActor
final class LaunchScreenModel {
nonisolated let analyticsName = "launch-details"
private(set) var title = "Select a launch"
nonisolated func analyticsEvent() -> String {
"screen_opened:\(analyticsName)"
}
func display(_ launch: LaunchDetails) {
title = launch.name
}
}
analyticsEvent() does not require main-actor state, so it can be called without entering MainActor. display(_:) changes UI-owned state and remains main-actor-isolated.
Again, the nonisolated method is not “background code.” It is simply callable from outside the model's global-actor isolation.
11 · TUTORIAL
A Complete Launch-Catalogue Design
A well-separated actor exposes stable metadata synchronously and protects changing catalogue data:
CODE EXAMPLE
struct LaunchCatalogIdentity: Hashable, Sendable {
let provider: String
let region: String
}
actor LaunchCatalog {
nonisolated let identity: LaunchCatalogIdentity
private var launchesByID: [String: Launch] = [:]
init(identity: LaunchCatalogIdentity) {
self.identity = identity
}
nonisolated var logPrefix: String {
"[\(identity.provider)/\(identity.region)]"
}
func replace(with launches: [Launch]) {
launchesByID = Dictionary(
uniqueKeysWithValues: launches.map { ($0.id, $0) }
)
}
func launch(id: String) -> Launch? {
launchesByID[id]
}
func snapshot() -> [Launch] {
Array(launchesByID.values)
}
}
extension LaunchCatalog: CustomStringConvertible {
nonisolated var description: String {
"LaunchCatalog \(logPrefix)"
}
}
Callers can use identity and diagnostic text immediately:
CODE EXAMPLE
let catalog = LaunchCatalog(
identity: LaunchCatalogIdentity(
provider: "Orbital API",
region: "Global"
)
)
print(catalog.logPrefix)
print(catalog.description)
await catalog.replace(with: launches)
let snapshot = await catalog.snapshot()
The absence or presence of await now tells the truth about ownership. Stable metadata is nonisolated. Mutable catalogue state is protected by the actor.
12 · TUTORIAL
Common Misunderstandings
“nonisolated sends work to a background thread.”
No. It removes an isolation requirement. It does not create or schedule asynchronous work.
“A nonisolated method can read any actor property.”
No. It cannot directly access actor-isolated mutable state. It may use data that is itself safely nonisolated.
“nonisolated makes a property immutable.”
No. Immutability and isolation are different properties. On an actor, explicitly nonisolated stored state exposed across concurrency domains must satisfy strict safety restrictions; the common design is an immutable Sendable value.
“A nonisolated async function needs no await.”
No. Callers still use await for an async function. What they avoid is a separate requirement to enter the surrounding actor merely because the method is its member.
“Adding nonisolated is always an optimization.”
No. It changes the declaration's isolation contract and limits which state it may access. Use it when the member genuinely does not belong to the actor's protected state.
13 · TUTORIAL
What to Remember
• Actor instance members are isolated to the actor by default.
• nonisolated opts a declaration out of that isolation.
• A synchronous nonisolated member can be called without await.
• A nonisolated member cannot directly access isolated mutable actor state.
• Immutable Sendable identity is a natural nonisolated use case.
• Nonisolated members can satisfy synchronous protocol requirements.
• nonisolated does not mean background, parallel or detached execution.
• An async nonisolated function still requires await.
• Use nonisolated because of data ownership, not to hide meaningful isolation boundaries.
14 · TUTORIAL
Frequently Asked Questions
What does nonisolated mean in Swift?
It means a declaration is not isolated to its surrounding actor or global actor. It can be used without entering that isolation domain.
Can nonisolated access actor properties?
It can access properties that are themselves safely nonisolated, such as immutable Sendable identity. It cannot directly access actor-isolated mutable state.
Does nonisolated run on a background thread?
No. The keyword describes actor isolation, not thread selection or task scheduling.
Why use nonisolated on an actor?
Use it for members that do not need protected state—for example, stable identifiers, diagnostic labels or implementations of synchronous protocol requirements.
Does a nonisolated async function still require await?
Yes. await is required because the function is async. Its nonisolated status means the function is not statically isolated to the surrounding actor.
What is nonisolated(nonsending)?
It is an explicit Swift 6.2 spelling for a nonisolated async function that runs on the caller's actor when one exists, without sending its arguments and result across an isolation boundary.
15 · TUTORIAL
References
16 · TUTORIAL
Continue Learning
nonisolated makes more sense once isolation domains are seen as an architectural tool rather than a thread instruction. The next article, What Is a Global Actor in Swift?, explains how one shared actor can isolate declarations spread across multiple types and files.
17 · TUTORIAL
Download the Xcode Playgrounds
Use Understanding nonisolated.playground to compare isolated and nonisolated actor members, then open nonisolated Challenges.playground to practise immutable identity, protocol conformances and compiler-guided boundary design.
