SwiftData操作ChatMessage报错:无法打开default.store或表不存在
错误现象
存储或查询ChatMessage时出现以下两类错误:
Query encountered an error: Error Domain=NSCocoaErrorDomain Code=256 "无法打开文件“default.store”。"
error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x3000eb800> , I/O error for database at /var/mobile/Containers/Data/Application/BE6DA08B-F6D6-4A52-9BCC-FD69F25A63F8/Library/Application Support/default.store. SQLite error code:1, 'no such table: ZCHATMESSAGE' with userInfo of {
NSFilePath = "/var/mobile/Containers/Data/Application/BE6DA08B-F6D6-4A52-9BCC-FD69F25A63F8/Library/Application Support/default.store";
NSSQLiteErrorDomain = 1;
}
相关代码
ChatMessage.swift
import Foundation import SwiftData // Define a ChatMessage model for storing chat history @Model final class ChatMessage { var id: UUID var content: String var sender: String // "user" or "ai" var timestamp: Date // Custom initializer for ChatMessage init(content: String, sender: String, timestamp: Date = Date()) { self.id = UUID() self.content = content self.sender = sender self.timestamp = timestamp } }
ChatView.swift
import SwiftUI import Foundation import SwiftData @MainActor struct ChatView: View { @Environment(\.modelContext) private var modelContext // State properties to hold user input and AI response @State private var userMessage: String = "" @State private var aiResponse: String = "Type a message and press 'Send' to start." @State private var products: [Product] = [] @State private var isLoading: Bool = false @Query(sort: \ChatMessage.timestamp, order: .forward) private var chatMessages: [ChatMessage] var body: some View { VStack { // Display chat history in a list ScrollViewReader { proxy in ScrollView { VStack(spacing: 10) { ForEach(chatMessages) { message in HStack { if message.sender == "user" { Spacer() Text(message.content) .padding() .background(Color.red) .foregroundColor(.white) .cornerRadius(10) } else { Text(message.content) .padding() .background(Color(.secondarySystemBackground)) .cornerRadius(10) Spacer() } } } } .padding(.horizontal) } .onChange(of: chatMessages.count) { if let lastMessage = chatMessages.last { proxy.scrollTo(lastMessage.id, anchor: .bottom) } } } Spacer() // Text input and send button at the bottom of the screen HStack { TextField("Enter your message", text: $userMessage, onCommit: { // Send message when user presses return Task { await sendChatRequest() } }) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.horizontal) // Dynamic button: Show ProgressView when loading, otherwise show send button if isLoading { ProgressView() .padding(.trailing, 10) } else { Button(action: { Task { await sendChatRequest() } }) { Image(systemName: "paperplane.fill") .foregroundStyle(.white) .padding(7) .background(Color.red) .cornerRadius(5) } .padding(.trailing, 10) } } .padding(.bottom) } .task { // Fetch data when the view appears await fetchProducts() } } // Function to fetch products from the Core Data context private func fetchProducts() async { do { let fetchDescriptor: FetchDescriptor<Product> = FetchDescriptor() products = try modelContext.fetch(fetchDescriptor) } catch { aiResponse = "Error fetching data: \(error.localizedDescription)" } } // Function to handle sending the chat request to the AI private func sendChatRequest() async { guard !userMessage.isEmpty else { let aiChatMessage = ChatMessage(content: "Please enter a message before sending", sender: "ai") modelContext.insert(aiChatMessage) return } // Create a new ChatMessage for the user's message and save it let userChatMessage = ChatMessage(content: userMessage, sender: "user") modelContext.insert(userChatMessage) // Set loading state to true isLoading = true // Fetch AI summary based on user message and fetched products if let aiResponseText = await chatBasedOnHistory(message: userMessage, products: products) { let aiChatMessage = ChatMessage(content: aiResponseText, sender: "ai") modelContext.insert(aiChatMessage) } else { let aiChatMessage = ChatMessage(content: "Failed to generate summary.", sender: "ai") modelContext.insert(aiChatMessage) } do { try modelContext.save() } catch { print("Failed to save context: \(error.localizedDescription)") } // Clear the text field after sending the message userMessage = "" // Reset loading state isLoading = false print("Number of messages: \(chatMessages.count)") for message in chatMessages { print("Message: \(message.content), Sender: \(message.sender)") } } }
AIController.swift
// Function to send the POST request and handle the response func getAISummary(jsonString: String) async -> String? { let parameters = [ "model": "llama-3.1-sonar-small-128k-online", "messages": [ [ "role": "system", "content": "Generate a concise summary of the overall quality and healthiness of the product based on the following product information." ], [ "role": "user", "content": jsonString ] ], "return_citations": true ] as [String : Any?] do { let postData = try JSONSerialization.data(withJSONObject: parameters, options: []) let url = URL(string: "https://api.perplexity.ai/chat/completions")! var request = URLRequest(url: url) request.httpMethod = "POST" request.timeoutInterval = 10 request.allHTTPHeaderFields = [ "accept": "application/json", "content-type": "application/json", "authorization": "// Secret Key" ] request.httpBody = postData let (data, _) = try await URLSession.shared.data(for: request) // Parse the JSON response to extract the generated summary if let responseJson = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let choices = responseJson["choices"] as? [[String: Any]], let message = choices.first?["message"] as? [String: Any], let content = message["content"] as? String { return content } } catch { print("Error during the request: \(error.localizedDescription)") } return nil } // Function to generate a summary using AI based on product history func chatBasedOnHistory(message: String, products: [Product]) async -> String? { // Convert products to a JSON-compatible dictionary or array let foodHistory: [[String: Any]] = products.map { product in return [ "name": product.name, "brand": product.brand, "quantity": product.quantity, "ingredients": product.ingredients, "nutritionScore": product.nutritionScore, ] } do { // Convert food history to JSON string let foodHistoryJSONData = try JSONSerialization.data(withJSONObject: foodHistory, options: []) let foodHistoryJSONString = String(data: foodHistoryJSONData, encoding: .utf8) ?? "" // Prepare parameters for the API request let parameters: [String: Any] = [ "model": "llama-3.1-sonar-small-128k-online", "messages": [ [ "role": "system", "content": message ], [ "role": "user", "content": "This is information on what I have eaten in the past: \(foodHistoryJSONString). \n\nKeep in mind that this is an app where the user can scan products and view their nutritional value. They can also keep a log of what they have eaten and see how many calories they have eaten this week and how much sugar they have eaten in the past week. Try to reccomend the user to use different parts of the app." ] ], "return_citations": true ] let postData = try JSONSerialization.data(withJSONObject: parameters, options: []) // Create the API request let url = URL(string: "https://api.perplexity.ai/chat/completions")! var request = URLRequest(url: url) request.httpMethod = "POST" request.timeoutInterval = 10 request.allHTTPHeaderFields = [ "accept": "application/json", "content-type": "application/json", "authorization": "Bearer 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" // Use your actual API key ] request.httpBody = postData // Perform the API request let (data, response) = try await URLSession.shared.data(for: request) // Check for successful response status if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { // Parse the JSON response to extract the generated summary if let responseJson = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let choices = responseJson["choices"] as? [[String: Any]], let message = choices.first?["message"] as? [String: Any], let content = message["content"] as? String { return content } } else { print("Unexpected response status code: \((response as? HTTPURLResponse)?.statusCode ?? -1)") print("Response body: \(String(data: data, encoding: .utf8) ?? "Unknown response")") } } catch { print("Error during the request or data fetching: \(error.localizedDescription)") } return nil }
App.swift
import SwiftUI import SwiftData @main struct CancerDetectorApp: App { @StateObject private var vm = AppViewModel() var sharedModelContainer: ModelContainer = { let schema = Schema([ Product.self, ChatMessage.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() var body: some Scene { WindowGroup { ContentView() .environmentObject(vm) .modelContainer(sharedModelContainer) } } }
解决方案
清理旧数据库文件
SQLite提示no such table: ZCHATMESSAGE,说明添加ChatMessage模型后,原有数据库未自动创建对应表。执行以下操作:- 在Xcode中选择
Product > Clean Build Folder - 删除模拟器中的应用,或在真机上卸载应用后重新安装
- 在Xcode中选择
验证模型容器配置
确认App.swift的Schema已正确包含ChatMessage.self,当前代码已满足该要求,无需修改。添加查询错误处理
在ChatView中为@Query添加错误捕获,便于排查后续问题:@Query(sort: \ChatMessage.timestamp, order: .forward) private var chatMessages: [ChatMessage] @Query private var queryError: Error?在视图中添加错误显示:
if let error = queryError { Text("Query error: \(error.localizedDescription)") .foregroundColor(.red) .padding() }确认线程安全
所有SwiftData操作(插入、保存、查询)均在@MainActor标记的视图中执行,符合主线程要求,当前代码无需调整。
内容的提问来源于stack exchange,提问作者Prathyush Yeturi

