From 92e393ab0389ace9e8154d965f2873ec9ef2a2dd Mon Sep 17 00:00:00 2001 From: Rune Olsen Date: Tue, 16 Jun 2026 14:59:07 +0200 Subject: [PATCH] Fix Swift 6 actor-isolation warnings in model inits and services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Message, Conversation, EmailLog: add nonisolated to inits — plain value types have no actor isolation, but the macOS 27 SDK was inferring it - EncryptionService: replace lazy var encryptionKey (which mutates self and gets inferred as @MainActor) with an eagerly-initialized let in init() - FileLogger: add nonisolated to shared, write, and minimumLevel so they are callable from nonisolated AppLogger methods without warnings - LogLevel.<: add nonisolated to the Comparable conformance method Co-Authored-By: Claude Sonnet 4.6 --- oAI/Models/Conversation.swift | 2 +- oAI/Models/EmailLog.swift | 2 +- oAI/Models/Message.swift | 2 +- oAI/Services/EncryptionService.swift | 17 +++++++---------- oAI/Utilities/Logging.swift | 10 +++++----- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/oAI/Models/Conversation.swift b/oAI/Models/Conversation.swift index bd33df7..6a1bb3d 100644 --- a/oAI/Models/Conversation.swift +++ b/oAI/Models/Conversation.swift @@ -33,7 +33,7 @@ struct Conversation: Identifiable, Codable { var updatedAt: Date var primaryModel: String? // Primary model used in this conversation - init( + nonisolated init( id: UUID = UUID(), name: String, messages: [Message] = [], diff --git a/oAI/Models/EmailLog.swift b/oAI/Models/EmailLog.swift index b2af49d..75ade66 100644 --- a/oAI/Models/EmailLog.swift +++ b/oAI/Models/EmailLog.swift @@ -44,7 +44,7 @@ struct EmailLog: Identifiable, Codable, Equatable { let responseTime: TimeInterval? // Time to generate response in seconds let modelId: String? // Model that handled the email - init( + nonisolated init( id: UUID = UUID(), timestamp: Date = Date(), sender: String, diff --git a/oAI/Models/Message.swift b/oAI/Models/Message.swift index 56c2e27..8914cf2 100644 --- a/oAI/Models/Message.swift +++ b/oAI/Models/Message.swift @@ -66,7 +66,7 @@ struct Message: Identifiable, Codable, Equatable { // Reasoning/thinking content (not persisted — in-memory only) var thinkingContent: String? = nil - init( + nonisolated init( id: UUID = UUID(), role: MessageRole, content: String, diff --git a/oAI/Services/EncryptionService.swift b/oAI/Services/EncryptionService.swift index 4ea0864..70711a4 100644 --- a/oAI/Services/EncryptionService.swift +++ b/oAI/Services/EncryptionService.swift @@ -31,12 +31,11 @@ import IOKit class EncryptionService { nonisolated static let shared = EncryptionService() - private let salt = "oAI-secure-storage-v1" // App-specific salt - private lazy var encryptionKey: SymmetricKey = { - deriveEncryptionKey() - }() + private let encryptionKey: SymmetricKey - private init() {} + private init() { + self.encryptionKey = Self.deriveEncryptionKey() + } // MARK: - Public Interface @@ -73,19 +72,17 @@ class EncryptionService { // MARK: - Key Derivation /// Derive encryption key from machine-specific data - private func deriveEncryptionKey() -> SymmetricKey { - // Combine machine UUID + bundle ID + salt for key material + private static func deriveEncryptionKey() -> SymmetricKey { let machineUUID = getMachineUUID() let bundleID = Bundle.main.bundleIdentifier ?? "com.oai.oAI" + let salt = "oAI-secure-storage-v1" let keyMaterial = "\(machineUUID)-\(bundleID)-\(salt)" - - // Hash to create consistent 256-bit key let hash = SHA256.hash(data: Data(keyMaterial.utf8)) return SymmetricKey(data: hash) } /// Get machine-specific UUID (IOPlatformUUID) - private func getMachineUUID() -> String { + private static func getMachineUUID() -> String { // Get IOPlatformUUID from IOKit let platformExpert = IOServiceGetMatchingService( kIOMainPortDefault, diff --git a/oAI/Utilities/Logging.swift b/oAI/Utilities/Logging.swift index 483fd92..93dac72 100644 --- a/oAI/Utilities/Logging.swift +++ b/oAI/Utilities/Logging.swift @@ -52,7 +52,7 @@ enum LogLevel: Int, Comparable, CaseIterable, Sendable { } } - static func < (lhs: LogLevel, rhs: LogLevel) -> Bool { + nonisolated static func < (lhs: LogLevel, rhs: LogLevel) -> Bool { lhs.rawValue < rhs.rawValue } } @@ -60,7 +60,7 @@ enum LogLevel: Int, Comparable, CaseIterable, Sendable { // MARK: - File Logger final class FileLogger: @unchecked Sendable { - static let shared = FileLogger() + nonisolated static let shared = FileLogger() private let fileHandle: FileHandle? private let queue = DispatchQueue(label: "com.oai.filelogger") @@ -70,8 +70,8 @@ final class FileLogger: @unchecked Sendable { return f }() - /// Current minimum log level (read from UserDefaults for thread safety) - var minimumLevel: LogLevel { + /// Current minimum log level (backed by UserDefaults — thread-safe). + nonisolated var minimumLevel: LogLevel { get { let raw = UserDefaults.standard.integer(forKey: "logLevel") return LogLevel(rawValue: raw) ?? .info @@ -95,7 +95,7 @@ final class FileLogger: @unchecked Sendable { fileHandle?.seekToEndOfFile() } - func write(_ level: LogLevel, category: String, message: String) { + nonisolated func write(_ level: LogLevel, category: String, message: String) { guard level >= minimumLevel else { return } queue.async { [weak self] in guard let self, let fh = self.fileHandle else { return }