~/wiki

Nonisolated Methods

Confiance : high
nonisolated-methodsswift-concurrencyactor-isolationthread-safetymain-actorconcurrency-controlmethod-isolationunsafe-concurrency

Swift concurrency annotation that allows methods within actor-isolated contexts to execute without actor isolation, enabling safe access from multiple threads without requiring async calls or actor hopping.

Core Concept

When a class is marked with @MainActor, all its methods are isolated to the main thread by default. The nonisolated keyword creates exceptions that can be called from any thread without async/await.

@MainActor
class AudioController {
    private let buffer = AudioBuffer()
    
    // Runs on main thread, requires await from other contexts
    func updateUI() {
        // UI updates here
    }
    
    // Can be called from any thread without await
    nonisolated func processAudio(_ samples: [Float]) {
        // Cannot access main-actor-isolated properties here
        processOnBackgroundThread(samples)
    }
}

Access Restrictions

Isolated Property Access: Nonisolated methods cannot directly access actor-isolated stored properties or call isolated methods:

@MainActor
class Controller {
    private var state: String = ""
    
    nonisolated func background() {
        // ❌ Error: cannot access main-actor-isolated property
        print(state)
        
        // ✅ OK: can access through async call
        Task { @MainActor in
            print(state)
        }
    }
}

Safe Access Patterns: Use immutable data, computed properties, or async closures for safe access:

@MainActor
class Controller {
    private let config: Config = Config() // immutable, OK to access
    
    nonisolated func process() {
        // ✅ Safe: immutable property
        let setting = config.defaultSetting
        
        // ✅ Safe: async main actor access
        Task { @MainActor in
            self.updateState(with: result)
        }
    }
}

Common Use Cases

Audio Processing Callbacks

AVAudioEngine callbacks need to execute on audio threads, not main thread:

@MainActor
class WakeWordDetector {
    private var isListening = false
    
    func startListening() {
        audioEngine.inputNode.installTap(on: 0, bufferSize: 1024, format: format) { [weak self] buffer, _ in
            self?.processAudioBuffer(buffer)
        }
    }
    
    nonisolated private func processAudioBuffer(_ buffer: AVAudioPCMBuffer) {
        // Safe to call from audio thread
        let samples = extractSamples(from: buffer)
        wakeWordQueue.async {
            self.runInference(samples)
        }
    }
}

Worker Queue Integration

Interface between main-actor-isolated controllers and background workers:

@MainActor
class Manager {
    private let worker = BackgroundWorker()
    
    nonisolated func enqueueWork(_ data: Data) {
        // Called from various threads, dispatches to worker
        worker.process(data) { [weak self] result in
            Task { @MainActor in
                self?.handleResult(result)
            }
        }
    }
}

Thread-Safe State Access

Enable safe access to immutable or carefully managed state:

@MainActor
class StateManager {
    private var _counter: Int = 0
    private let counterQueue = DispatchQueue(label: "counter")
    
    nonisolated func incrementCounter() {
        counterQueue.async {
            self._counter += 1
        }
    }
    
    nonisolated func getCounter() -> Int {
        counterQueue.sync { _counter }
    }
}

Unsafe Patterns

nonisolated(unsafe): More dangerous variant that bypasses safety checks entirely:

@MainActor
class UnsafeController {
    private var dangerousState: String = ""
    
    nonisolated(unsafe) func directAccess() {
        // ⚠️ Compiler allows this but creates race conditions
        dangerousState = "modified from any thread"
    }
}

Use nonisolated(unsafe) only when you've manually verified thread safety through other means (locks, atomic operations, etc.).

Best Practices

Immutable Access Only: Prefer accessing only immutable properties or constants from nonisolated methods.

Async Callbacks: Use Task { @MainActor in } to safely access isolated state from nonisolated contexts.

Worker Delegation: Delegate heavy work to separate classes that don't need actor isolation.

Clear Threading Model: Document which methods can be called from which threads, especially in audio processing contexts.

Avoid Shared Mutable State: Design APIs to minimize shared mutable state between isolated and nonisolated contexts.

See Also