使用CoreData保存含字典的数组时遇崩溃问题求助
首先得明确:CoreData原生不支持直接存储元组(Tuple),也没法直接把包含元组的数组作为属性赋值给NSManagedObject——这就是你保存上下文时崩溃的核心原因。CoreData只支持有限的可持久化类型(比如String、Number、Data、Dictionary,以及符合要求的Transformable类型),元组不在这个列表里。
下面给你两种可行的解决方案,你可以根据自己的需求选择:
方案一:用Codable序列化+Transformable属性(简单直接)
这种方式适合你不需要单独查询数组里的单个元素,只是想整体存储和读取的场景:
把元组转换成Codable结构体
Swift的元组默认不支持Codable,所以咱们先把你的元组转换成一个符合Codable的结构体,这样就能方便地序列化/反序列化了:struct DataItem: Codable { let value1: Float let value2: Float let info: [String: String] }然后把原来的
DataSet数组转换成[DataItem]类型:let dataItems: [DataItem] = DataSet.map { DataItem(value1: $0.0, value2: $0.1, info: $0.2) }调整xcdatamodel中的属性
在你的实体里添加一个属性(比如叫dataSet):- 类型选择
Transformable - 如果是iOS 13及以上版本,直接在"Class"字段填写
[DataItem],CoreData会自动用Codable处理序列化 - 如果是iOS 13以下,需要自定义ValueTransformer,步骤往下看
- 类型选择
iOS 13以下的自定义Transformer(可选)
如果你需要兼容更低版本,得写一个自定义的ValueTransformer来处理数组的编码和解码:class DataItemArrayTransformer: ValueTransformer { override class func transformedValueClass() -> AnyClass { return NSData.self } override class func allowsReverseTransformation() -> Bool { return true } override func transformedValue(_ value: Any?) -> Any? { guard let items = value as? [DataItem] else { return nil } return try? JSONEncoder().encode(items) } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? Data else { return nil } return try? JSONDecoder().decode([DataItem].self, from: data) } }然后在App启动时(比如
AppDelegate的didFinishLaunchingWithOptions里)注册这个Transformer:ValueTransformer.setValueTransformer(DataItemArrayTransformer(), forName: NSValueTransformerName(rawValue: "DataItemArrayTransformer"))最后回到xcdatamodel,把属性的"Transformer Name"设为
DataItemArrayTransformer即可。
方案二:拆分实体用关系存储(更符合CoreData设计)
如果你需要单独查询、修改数组里的某个元素,或者数据量较大,建议用CoreData的关系特性来拆分模型:
创建子实体
在xcdatamodel里新增一个实体,比如叫DataItemEntity,添加三个属性:value1:类型Floatvalue2:类型Floatinfo:类型Dictionary(CoreData支持存储[String:String]这种字典)
建立一对多关系
在你的主实体里添加一个一对多的关系(比如叫items),目标实体选择DataItemEntity,同时在DataItemEntity里添加一个反向关系(比如叫parentDataSet)关联回主实体。存储数据时逐个创建子实体
遍历你的DataSet数组,为每个元组创建一个DataItemEntity实例,然后关联到主实体的关系属性上:for tuple in DataSet { let itemEntity = DataItemEntity(context: managedContext) itemEntity.value1 = tuple.0 itemEntity.value2 = tuple.1 itemEntity.info = tuple.2 mainEntity.items?.adding(itemEntity) }
这种方案的好处是可以单独操作每个数据项,比如查询某个value1大于某个值的项,而不需要把整个数组读出来;缺点是需要多写一些实体关联的代码。
注意事项
- 永远不要直接把CoreData不支持的类型(比如元组、自定义非Codable结构体)赋值给NSManagedObject的属性,一定要转换成支持的类型或者用上述方案处理。
- 如果用Transformable方案,序列化后的数据是存在Data字段里的,没法直接在CoreData的查询语句(NSPredicate)里使用数组内的元素,这点要注意。
内容的提问来源于stack exchange,提问作者LostReminder

