Swift SDK fails to compile under Xcode 27 / iOS 27 SDK: AVAudioPlayerDelegate is now @MainActor

Author: WolfTheDesignerCreated Sep 16, 2026Updated Sep 16, 2026

The Swift SDK does not compile against the iOS 27 SDK. The cause is a source-compatibility change in Apple's SDK rather than anything in this repository, but it makes the RunAnywhere target — and therefore every consumer — unbuildable on Xcode 27.

Cause

The iOS 27 SDK annotates AVAudioPlayerDelegate with NS_SWIFT_UI_ACTOR, which imports into Swift as @MainActor:

objc
// iPhoneSimulator27.0.sdk/System/Library/Frameworks/AVFAudio.framework/Headers/AVAudioPlayer.h:126
NS_SWIFT_UI_ACTOR
@protocol AVAudioPlayerDelegate <NSObject>

The same declaration in the iOS 26.5 SDK carries no actor annotation (__WATCHOS_AVAILABLE(3_0)). Diffing every framework header between the two SDKs, this is the only protocol that gained the annotation — 188 annotated protocols in 26.5, 189 in 27.0.

Under SE-0316, a type that declares conformance to a global-actor-isolated protocol in its own declaration, with no explicit isolation of its own, is inferred to be isolated to that actor — including its init() and all members. AudioPlaybackManager declares exactly that:

swift
// bindings/swift/Sources/RunAnywhere/Features/TTS/Services/AudioPlaybackManager.swift:37
public class AudioPlaybackManager: NSObject, ObservableObject, AVAudioPlayerDelegate, @unchecked Sendable {

so under the iOS 27 SDK the whole class silently becomes @MainActor. @unchecked Sendable does not prevent this — it suppresses Sendable conformance checking, not actor-isolation inference.

Resulting errors

Nonisolated use sites then violate isolation. On main these are:

Site Diagnostic
bindings/swift/…/Public/API/Namespaces/TTSNamespace.swift:133private static let speechPlayback = AudioPlaybackManager() main actor-isolated default value in a nonisolated context
bindings/swift/…/Public/API/Namespaces/TTSNamespace.swift:227,274speechPlayback.stop() main actor-isolated instance method stop() cannot be called from outside of the actor
bindings/swift/…/Features/VoiceAgent/Services/VoiceAgentMicDriver.swift:35private let playback = AudioPlaybackManager() main actor-isolated default value in a nonisolated context

Because Package.swift declares swift-tools-version: 6.2 (Swift 6 language mode), these are errors rather than warnings. LlamaCPPRuntime then fails with Unable to resolve module dependency: 'RunAnywhere', and consumers additionally see Missing package product 'App' as a downstream effect.

Verified on the v0.20.10 tag; main still declares the conformance the same way, so the defect is present there too.

Why this is not just a missing annotation

AudioPlaybackManager is deliberately thread-agnostic: its mutable State is guarded by an OSAllocatedUnfairLock, it declares @unchecked Sendable with a comment about lock-held access to non-Sendable AVFoundation objects, and its own doc comment demonstrates unisolated construction and use. Annotating the class @MainActor would compile, but it would relocate TTS playback and the voice-agent mic driver onto the main actor — a behaviour change rather than a compile fix.

Suggested fix

Move the conformance into an extension. Global-actor inference applies only when the conformance is stated in the type's own declaration, so this stops the inference while still satisfying the protocol; marking the four callbacks nonisolated preserves today's threading contract exactly.

diff
-public class AudioPlaybackManager: NSObject, ObservableObject, AVAudioPlayerDelegate, @unchecked Sendable {
+public class AudioPlaybackManager: NSObject, ObservableObject, @unchecked Sendable {

and, after the class body:

swift
// MARK: - AVAudioPlayerDelegate

extension AudioPlaybackManager: AVAudioPlayerDelegate {
    nonisolated public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {  }
    nonisolated public func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {  }

    #if os(iOS) || os(tvOS)
    nonisolated public func audioPlayerBeginInterruption(_ player: AVAudioPlayer) {  }
    nonisolated public func audioPlayerEndInterruption(_ player: AVAudioPlayer, withOptions flags: Int) {  }
    #endif
}

The four callback bodies move across unchanged; deinit stays in the class body, since deinit cannot be declared in an extension. player.delegate = self needs no change. This compiles on both the iOS 26 and iOS 27 SDKs and is source-compatible for existing consumers.

An equivalent alternative is to declare the class nonisolated to opt out of inference and annotate only the four delegate methods @MainActor, though that depends on the newer type-level nonisolated spelling.

Verification

Applied the change above to v0.20.10 in a consuming iOS app and built under Xcode 27.0 (27A266a), Swift 6.4, against iPhoneSimulator27.0.sdk: the build goes from 57 errors (all of them this issue and its cascade) to zero, with no other dependency in the graph affected.

Reproduction

  1. Resolve the package from a project targeting iOS.
  2. Build any target depending on the RunAnywhere product with Xcode 27.
  3. RunAnywhere fails with the three diagnostics above; the identical source builds with Xcode 26.6 (iOS 26.5 SDK).

Source: RunanywhereAI/runanywhere-sdks