fix(ios): add Swift 6 strict concurrency compatibility

Applies the same Swift 6 compatibility patterns from PR #166 (macOS) to the iOS app.

Changes:
- LocationService.swift: Added Sendable constraint to withTimeout<T> generic,
  made CLLocationManagerDelegate methods nonisolated with Task { @MainActor in }
  pattern to safely access MainActor state
- TalkModeManager.swift: Fixed OSLog string interpolation to avoid operator
  overload issues with OSLogMessage in Swift 6

Addresses #164

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kristijan Jovanovski
2026-01-10 15:51:28 +01:00
committed by Peter Steinberger
parent d1943a9337
commit e4fea2b80b
2 changed files with 30 additions and 23 deletions

View File

@@ -86,9 +86,9 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
} }
} }
private func withTimeout<T>( private func withTimeout<T: Sendable>(
timeoutMs: Int, timeoutMs: Int,
operation: @escaping () async throws -> T) async throws -> T operation: @escaping @Sendable () async throws -> T) async throws -> T
{ {
if timeoutMs == 0 { if timeoutMs == 0 {
return try await operation() return try await operation()
@@ -117,26 +117,35 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
} }
} }
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
Task { @MainActor in
if let cont = self.authContinuation { if let cont = self.authContinuation {
self.authContinuation = nil self.authContinuation = nil
cont.resume(returning: manager.authorizationStatus) cont.resume(returning: status)
}
} }
} }
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locs = locations
Task { @MainActor in
guard let cont = self.locationContinuation else { return } guard let cont = self.locationContinuation else { return }
self.locationContinuation = nil self.locationContinuation = nil
if let latest = locations.last { if let latest = locs.last {
cont.resume(returning: latest) cont.resume(returning: latest)
} else { } else {
cont.resume(throwing: Error.unavailable) cont.resume(throwing: Error.unavailable)
} }
} }
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) { nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) {
let err = error
Task { @MainActor in
guard let cont = self.locationContinuation else { return } guard let cont = self.locationContinuation else { return }
self.locationContinuation = nil self.locationContinuation = nil
cont.resume(throwing: error) cont.resume(throwing: err)
}
} }
} }

View File

@@ -288,9 +288,8 @@ final class TalkModeManager: NSObject {
self.chatSubscribedSessionKeys.insert(key) self.chatSubscribedSessionKeys.insert(key)
self.logger.info("chat.subscribe ok sessionKey=\(key, privacy: .public)") self.logger.info("chat.subscribe ok sessionKey=\(key, privacy: .public)")
} catch { } catch {
self.logger.warning( let err = error.localizedDescription
"chat.subscribe failed sessionKey=\(key, privacy: .public) " + self.logger.warning("chat.subscribe failed key=\(key, privacy: .public) err=\(err, privacy: .public)")
"err=\(error.localizedDescription, privacy: .public)")
} }
} }
@@ -528,9 +527,8 @@ final class TalkModeManager: NSObject {
self.lastPlaybackWasPCM = false self.lastPlaybackWasPCM = false
result = await self.mp3Player.play(stream: stream) result = await self.mp3Player.play(stream: stream)
} }
self.logger.info( let duration = Date().timeIntervalSince(started)
"elevenlabs stream finished=\(result.finished, privacy: .public) " + self.logger.info("elevenlabs stream finished=\(result.finished, privacy: .public) dur=\(duration, privacy: .public)s")
"dur=\(Date().timeIntervalSince(started), privacy: .public)s")
if !result.finished, let interruptedAt = result.interruptedAt { if !result.finished, let interruptedAt = result.interruptedAt {
self.lastInterruptedAtSeconds = interruptedAt self.lastInterruptedAtSeconds = interruptedAt
} }