Threading model (iOS)
How the Extentos iOS SDK concurrency works — ExtentosGlasses is Sendable, every sub-client call is async, MainActor guidance for UI, and why the package builds in Swift 5 language mode.
The concurrency contract below is verified against the current iOS source. Package install: Install.
Sendable instance, async calls
ExtentosGlasses is Sendable — you can hold one instance and call it from any task or actor without a data race. There's no requirement to confine it to a particular thread. Create it once (see Initialization) and share it.
Every capability call is async. Reach a primitive through its sub-client and await it — lifecycle and capture calls return an ExtentosResult to pattern-match (or discard explicitly):
let result = await glasses.camera.capturePhoto() // ExtentosResult<Photo, CaptureError>
if case .success(let photo) = result { /* use photo */ }
_ = await glasses.audio.speak("Done.") // ExtentosResult<Void, AudioError>handleUrl(_:) and Extentos.requestSpeechRecognitionAuthorization() are likewise async. The SDK runs its work off the main thread; you don't manually hop queues to call it.
MainActor and UI
Because the SDK is Sendable and its methods are async, the idiomatic pattern is to call it from a Task and apply results to your UI on the MainActor:
@MainActor
final class CaptureViewModel: ObservableObject {
@Published var lastPhoto: Photo?
let glasses: ExtentosGlasses
func capture() {
Task {
// capturePhoto() returns ExtentosResult<Photo, CaptureError> — pattern-match it, don't assign directly
if case .success(let photo) = await glasses.camera.capturePhoto() {
// back on the MainActor — safe to mutate @Published state
self.lastPhoto = photo
}
}
}
}SwiftUI view modifiers like .onOpenURL already run on the main actor; spawning a Task { await glasses.handleUrl(url) } inside one (as in Initialization) moves the SDK work off the main thread while keeping the modifier's contract.
Streams and cancellation
Continuous primitives (transcription, frame streams, connection-state updates) are surfaced as async sequences. Iterate them inside a Task and cancel by cancelling that task — structured concurrency tears the iteration down. Store long-lived Task handles on your view model and cancel them in deinit or when the view disappears, so a stream doesn't outlive the screen that started it.
Swift 6 toolchain, Swift 5 language mode
The package declares swift-tools-version: 6.0, so it builds with a Swift 6 toolchain — but it compiles in the Swift 5 language mode (.swiftLanguageMode(.v5) on the GlassesCore target). The reason: the uniffi-generated Swift bindings the core ships use a global var that Swift 6's strict concurrency checking rejects. Until those bindings are isolated or regenerated clean, the language mode stays at 5.
What this means for you:
- "Swift 6" unqualified overstates it — say Swift 6 toolchain, Swift 5 language mode.
- The SDK's public API is unaffected — the
Sendable/asynccontract above holds either way. - Your own app can adopt Swift 6 strict concurrency independently; the language-mode setting is scoped to the SDK's own target, not yours.
This is a tracked SDK-internal follow-up, not something you need to work around.
See Android threading for the Kotlin-coroutines counterpart.
Related
Initialization (iOS)
Initialize the Extentos iOS SDK with Extentos.create(config:), forward the Meta DAT auth callback via .onOpenURL and handleUrl, request speech authorization, and reach the typed sub-clients.
Lifecycle (iOS)
The Extentos iOS SDK lifecycle — create one instance for the app's lifetime, forward the auth callback, and tear down with the async shutdown().
Android SDK threading model
How the Extentos Android SDK uses Kotlin coroutines and Flow — suspend functions for one-shot ops, Flow/StateFlow for streams, and which dispatcher to collect on.
Lifecycle (iOS)
The Extentos iOS SDK lifecycle — create one instance for the app's lifetime, forward the auth callback, and tear down with the async shutdown().
Runtime internals (iOS)
How the Extentos iOS Swift shell wraps the shared Rust core via uniffi, where the DAT-iOS transport bridge sits, and what's platform-bound vs shared.