Swift SDK fails to compile under Xcode 27 / iOS 27 SDK: AVAudioPlayerDelegate is now @MainActor
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:
// 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:
// 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:133 — private static let speechPlayback = AudioPlaybackManager() |
main actor-isolated default value in a nonisolated context |
bindings/swift/…/Public/API/Namespaces/TTSNamespace.swift:227,274 — speechPlayback.stop() |
main actor-isolated instance method stop() cannot be called from outside of the actor |
bindings/swift/…/Features/VoiceAgent/Services/VoiceAgentMicDriver.swift:35 — private 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.
-public class AudioPlaybackManager: NSObject, ObservableObject, AVAudioPlayerDelegate, @unchecked Sendable {
+public class AudioPlaybackManager: NSObject, ObservableObject, @unchecked Sendable {and, after the class body:
// 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
- Resolve the package from a project targeting iOS.
- Build any target depending on the
RunAnywhereproduct with Xcode 27. RunAnywherefails with the three diagnostics above; the identical source builds with Xcode 26.6 (iOS 26.5 SDK).
Source: RunanywhereAI/runanywhere-sdks