Swift中CoreData UserEntity'unrecognized selector'错误排查与解决
开发集成CoreData与登录功能的App时,通过LoginView和LoginViewModel将用户信息传递至ContentView,运行时触发崩溃:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[abcText2Speech.UserEntity firstname]: unrecognized selector sent to instance 0x6000017f14c0'
相关代码片段
LoginViewModel中的signup方法
func signup()-> (UserEntity, Int){ @StateObject var dataController = DataController.shared buttonState = .loading(title: "Loading", systemImage: "person") DispatchQueue.main.asyncAfter(deadline: .now()+1.5){ if self.isValidEmail() && self.isValidPassword() && self.isValidName(){ self.isLoggedIn = true dataController.addUser(firstname: self.fname, lastname: self.lname, email: self.email, password: self.password) print("signed in") print(dataController.savedEntities[(dataController.savedEntities.count)-1].email ?? "email") print(self.isLoggedIn) self.buttonState = .enabled(title: "Login", systemImage: "checkmark.circle") } else{ self.buttonState = .disabled(title: "Fill out all fields to log in", systemImage: "exclamationmark.circle") } } print("Test") print(dataController.savedEntities[(dataController.savedEntities.count)-1].email ?? "email") return (dataController.savedEntities[(dataController.savedEntities.count)-1], 1) }
注:此方法内的打印内容均正常输出。
SignUpView相关代码
ActionButton(state: $model.buttonState, onTap:{ signup = model.signup() user = signup.0 }, backgroundColor: .primary , foregroundColor: Color(UIColor.systemBackground) ) NavigationLink (destination: LoginView()){ Text("Already have a profile? Click here to Log in") } } } label: { Label("Welcome!", systemImage: "hand.wave") } .padding() .textFieldStyle(.plain) let keyboard = abcTextViewModel() NavigationLink (destination: ContentView(viewModel: keyboard, currentUser: user) , isActive: $model.isLoggedIn){ EmptyView() }
ContentView触发错误的代码
Text(currentUser.firstname ?? "name")
DataController代码
import Foundation import CoreData class DataController: ObservableObject{ static let shared = DataController() let container: NSPersistentContainer @Published var savedEntities: [UserEntity] = [] init(){ container = NSPersistentContainer(name: "Model") container.loadPersistentStores { description, error in if let error = error { print("Core Data failed to load: \(error.localizedDescription)") } } fetchUsers() } func fetchUsers() { let request = NSFetchRequest<UserEntity>(entityName: "UserEntity") do { savedEntities = try container.viewContext.fetch(request) } catch let error { print("Error while fetching: \(error)") } } func addUser(firstname: String, lastname: String, email: String, password: String) { let newUser = UserEntity(context: container.viewContext) newUser.firstname = firstname newUser.lastname = lastname newUser.email = email newUser.id = password saveData() } func saveData(){ do { try container.viewContext.save() fetchUsers() } catch let error { print("Error saving: \(error)") } } }
崩溃触发位置(abcText2SpeechApp结构体)
import SwiftUI @main struct abcText2SpeechApp: App { var body: some Scene { WindowGroup{ SignUpView() .environment(\.managedObjectContext, DataController.shared.container.viewContext) } } }
问题原因及解决方案
核心问题1:异步操作导致返回的UserEntity无效
signup()方法内通过DispatchQueue.main.asyncAfter延迟执行用户创建逻辑,但方法本身会立即返回当前的最后一个UserEntity——此时新用户尚未创建,返回的是旧数据(甚至可能触发数组越界)。异步代码执行完成后,传递给ContentView的仍是之前返回的无效实体,调用firstname时触发找不到方法的崩溃。
核心问题2:@StateObject声明位置错误
@StateObject var dataController = DataController.shared写在方法内部是错误的,@StateObject仅能用于View或ObservableObject的属性中,方法内声明会导致CoreData上下文混乱。
修复步骤
重构LoginViewModel的signup方法
移除方法返回值,通过@Published属性传递最新用户:class LoginViewModel: ObservableObject { // 保留原有属性(fname、lname、email、password、isLoggedIn、buttonState等) @Published var currentUser: UserEntity? let dataController = DataController.shared // 移至属性位置,使用let而非@StateObject func signup() { buttonState = .loading(title: "Loading", systemImage: "person") DispatchQueue.main.asyncAfter(deadline: .now()+1.5){ guard self.isValidEmail(), self.isValidPassword(), self.isValidName() else { self.buttonState = .disabled(title: "Fill out all fields to log in", systemImage: "exclamationmark.circle") return } self.dataController.addUser(firstname: self.fname, lastname: self.lname, email: self.email, password: self.password) // 取最新创建的用户 if let newUser = self.dataController.savedEntities.last { self.currentUser = newUser } self.isLoggedIn = true self.buttonState = .enabled(title: "Login", systemImage: "checkmark.circle") print("signed in") print(self.dataController.savedEntities.last?.email ?? "email") print(self.isLoggedIn) } } }修改SignUpView的跳转逻辑
绑定ViewModel的currentUser,确保仅当用户实体存在时才跳转:struct SignUpView: View { @StateObject var model = LoginViewModel() @State private var keyboard = abcTextViewModel() var body: some View { // 原有视图代码... ActionButton(state: $model.buttonState, onTap:{ model.signup() }, backgroundColor: .primary , foregroundColor: Color(UIColor.systemBackground)) NavigationLink( destination: ContentView(viewModel: keyboard, currentUser: model.currentUser!), isActive: $model.isLoggedIn, label: { EmptyView() } ) .disabled(model.currentUser == nil) // 防止用户未创建完成时跳转 NavigationLink(destination: LoginView()){ Text("Already have a profile? Click here to Log in") } } }校验CoreData实体属性
确认CoreData模型文件中,UserEntity确实存在名为firstname的属性(注意大小写匹配,若模型中为firstName,需同步修改代码中的调用为currentUser.firstName)。消除数组越界风险
替换dataController.savedEntities[(dataController.savedEntities.count)-1]为dataController.savedEntities.last,并添加可选绑定判断,避免数组为空时的崩溃。
内容的提问来源于stack exchange,提问作者Jaanvi chirimar

