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) {
if let cont = self.authContinuation { let status = manager.authorizationStatus
self.authContinuation = nil Task { @MainActor in
cont.resume(returning: manager.authorizationStatus) if let cont = self.authContinuation {
self.authContinuation = nil
cont.resume(returning: status)
}
} }
} }
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let cont = self.locationContinuation else { return } let locs = locations
self.locationContinuation = nil Task { @MainActor in
if let latest = locations.last { guard let cont = self.locationContinuation else { return }
cont.resume(returning: latest) self.locationContinuation = nil
} else { if let latest = locs.last {
cont.resume(throwing: Error.unavailable) cont.resume(returning: latest)
} else {
cont.resume(throwing: Error.unavailable)
}
} }
} }
func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) { nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) {
guard let cont = self.locationContinuation else { return } let err = error
self.locationContinuation = nil Task { @MainActor in
cont.resume(throwing: error) guard let cont = self.locationContinuation else { return }
self.locationContinuation = nil
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
} }