升级macOS 15后CoreData Transformable属性失效求助
fault: Could not materialize Objective-C class for declared attribute value class name "ContactContainer" of attribute named contactContainer
CoreData: fault: Could not materialize Objective-C class for declared attribute value class name "ContactContainer" of attribute named contactContainer
CoreData: fault: Could not materialize Objective-C class for declared attribute value class name "ContactContainer" of attribute named contactContainer
升级至新系统版本前一切正常,不确定是否是系统设置变更导致问题。已尝试重装整机、重启、清理缓存、重装Xcode、从零重装应用、恢复Time Machine备份、删除容器等操作,但应用完全无法运行,无法登录也无法打开任何窗口。
Transformer类代码
import Foundation @objc(ContactValueTransformer) public final class ContactValueTransformer: ValueTransformer { static let name = NSValueTransformerName(rawValue: String(describing: ContactValueTransformer.self)) override public class func transformedValueClass() -> AnyClass { ContactContainer.self } override public class func allowsReverseTransformation() -> Bool { true } override public func transformedValue(_ value: Any?) -> Any? { guard let contact = value as? ContactContainer else { return nil } do { let data = try JSONEncoder().encode(contact) return data } catch { assertionFailure("Failed to transform `ContactContainer` to `Data`") return nil } } override public func reverseTransformedValue(_ value: Any?) -> Any? { if value == nil { return nil } guard let data = value as? Data else { return nil } do { return try JSONDecoder().decode(ContactContainer.self, from: data) } catch { assertionFailure("Failed to transform `Data` to `ContactContainer`") return nil } } /// Registers the value transformer with `ValueTransformer`. public static func register() { let transformer = ContactValueTransformer() ValueTransformer.setValueTransformer(transformer, forName: name) } }
ContactContainer类代码
import Foundation import SwiftUI public class ContactContainer: NSObject, Codable, ObservableObject { public var phones: [Phone] = [] { didSet { lastModified = .now objectWillChange.send() } } public var emails: [Email] = [] { didSet { lastModified = .now objectWillChange.send() } } public var addresses: [Address] = [] { didSet { lastModified = .now objectWillChange.send() } } private enum CodingKeys: CodingKey { case phones case emails case addresses case lastModified case dateCreated } public private(set) var lastModified: Date public private(set) var dateCreated: Date public override init() { self.phones = [] self.emails = [] self.addresses = [] self.lastModified = .now self.dateCreated = .now super.init() } public init(phone: Phone? = nil, email: Email? = nil, address: Address? = nil) { if let phone { self.phones = [phone] } if let email { self.emails = [email] } if let address { self.addresses = [address] } self.lastModified = .now self.dateCreated = .now } public init(phones: [Phone] = [], emails: [Email] = [], addresses: [Address] = []) { self.phones = phones self.emails = emails self.addresses = addresses self.lastModified = .now self.dateCreated = .now } public required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.phones = try container.decode([Phone].self, forKey: .phones) self.emails = try container.decode([Email].self, forKey: .emails) self.addresses = try container.decode([Address].self, forKey: .addresses) self.lastModified = try container.decode(Date.self, forKey: .lastModified) self.dateCreated = try container.decode(Date.self, forKey: .dateCreated) } public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.phones, forKey: .phones) try container.encode(self.emails, forKey: .emails) try container.encode(self.addresses, forKey: .addresses) try container.encode(self.lastModified, forKey: .lastModified) try container.encode(self.dateCreated, forKey: .dateCreated) } public func hasMatch(for searchText: String) -> Bool { addresses.hasMatch(for: searchText) || phones.hasMatch(for: searchText) || emails.hasMatch(for: searchText) } public func copy() throws -> ContactContainer { try JSONDecoder().decode(Self.self, from: try JSONEncoder().encode(self)) } }
注:Phone、Address和Email类的Codable协议实现基本一致,copy方法用于更新NSManagedObject的数据。
Core Data模型截图
![Core Data模型属性设置截图]
![Core Data模型Transformer设置截图]
问题根源
这个错误的核心是Core Data无法通过Objective-C runtime找到ContactContainer类,新版本系统对Core Data的Objective-C绑定要求更严格,原代码中ContactContainer未显式暴露给Objective-C,导致识别失败。
修复步骤
- 给ContactContainer添加Objective-C暴露标记
修改ContactContainer类定义,添加@objc(ContactContainer),确保Objective-C runtime能识别该类:
@objc(ContactContainer) public class ContactContainer: NSObject, Codable, ObservableObject { // 原有代码保持不变 }
- 确保Value Transformer尽早注册
在App启动的最早期阶段注册Transformer,比如在App结构体的init方法中:
@main struct YourAppName: App { init() { ContactValueTransformer.register() } var body: some Scene { WindowGroup { ContentView() } } }
- 验证Core Data模型设置
确认模型中:
contactContainer属性类型为Transformable- Value Transformer选择
ContactValueTransformer - 自定义类名称为
ContactContainer(需与@objc标记的类名完全一致)
- 清理重建项目
- 执行
Product > Clean Build Folder - 删除设备上的应用
- 重新编译运行
内容的提问来源于stack exchange,提问作者user20325868

