如何将CoreML模型输出的MLMultiArray持久化到Core Data?
持久化CoreML生成的MLMultiArray到Core Data的可行方案
方案一:用Core Data Transformable类型(自定义转换器)
Transformable类型完全适用,但直接依赖默认转换器可能存在兼容性问题,建议自定义ValueTransformer来处理MLMultiArray与可存储数据的双向转换:
- 实现自定义转换器
class MLMultiArrayTransformer: ValueTransformer { override class func transformedValueClass() -> AnyClass { return Data.self } override class func allowsReverseTransformation() -> Bool { return true } override func transformedValue(_ value: Any?) -> Any? { guard let multiArray = value as? MLMultiArray else { return nil } guard multiArray.dataType == .float32 else { return nil } let count = multiArray.count let floatPointer = multiArray.dataPointer.bindMemory(to: Float32.self, capacity: count) let buffer = UnsafeBufferPointer(start: floatPointer, count: count) return Data(buffer: buffer) } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? Data else { return nil } let count = data.count / MemoryLayout<Float32>.stride guard let multiArray = try? MLMultiArray(shape: [NSNumber(value: count)], dataType: .float32) else { return nil } data.copyBytes(to: multiArray.dataPointer.bindMemory(to: Float32.self, capacity: count)) return multiArray } }
- 注册转换器
在App启动阶段(比如AppDelegate或SceneDelegate的初始化方法)注册转换器:
ValueTransformer.setValueTransformer(MLMultiArrayTransformer(), forName: NSValueTransformerName(rawValue: "MLMultiArrayTransformer"))
- 配置Core Data实体
- 在Core Data模型编辑器中添加属性,类型选择
Transformable - 在属性的「Transformer」栏填写
MLMultiArrayTransformer(与注册名称一致) - 可选勾选「Allow External Storage」,适合存储较大的数组
- 读写示例
// 存储 let predictions = try model.prediction(from: input) let record = PredictionRecord(context: viewContext) record.predictionArray = predictions.yourMultiArrayOutput // 读取 if let storedArray = record.predictionArray as? MLMultiArray { // 使用存储的MLMultiArray }
方案二:转换为Data后存储到Binary Data字段
如果不想用Transformable,直接将MLMultiArray转为Data,存储到Core Data的Binary Data类型字段,可控性更强:
配置Core Data实体
添加一个Binary Data类型的属性,可选勾选「Allow External Storage」;建议额外添加一个Transformable属性存储数组的shape(还原时需要)。转换工具方法
// MLMultiArray转Data func multiArrayToData(_ array: MLMultiArray) -> Data? { guard array.dataType == .float32 else { return nil } let count = array.count let floatPointer = array.dataPointer.bindMemory(to: Float32.self, capacity: count) let buffer = UnsafeBufferPointer(start: floatPointer, count: count) return Data(buffer: buffer) } // Data转MLMultiArray(需要传入原shape) func dataToMultiArray(_ data: Data, shape: [NSNumber]) -> MLMultiArray? { let expectedCount = shape.reduce(1) { $0 * $1.intValue } let actualCount = data.count / MemoryLayout<Float32>.stride guard expectedCount == actualCount else { return nil } guard let array = try? MLMultiArray(shape: shape, dataType: .float32) else { return nil } data.copyBytes(to: array.dataPointer.bindMemory(to: Float32.self, capacity: actualCount)) return array }
- 读写示例
// 存储 let predictions = try model.prediction(from: input) let record = PredictionRecord(context: viewContext) record.arrayData = multiArrayToData(predictions.yourMultiArrayOutput) record.arrayShape = predictions.yourMultiArrayOutput.shape // 读取 if let data = record.arrayData, let shape = record.arrayShape as? [NSNumber], let array = dataToMultiArray(data, shape: shape) { // 使用还原后的MLMultiArray }
方案三:结构化存储(适合简单维度数组)
如果你的MLMultiArray是一维或固定结构的,可转为JSON字符串存储到Core Data的String字段:
// MLMultiArray转JSON字符串 func multiArrayToJSON(_ array: MLMultiArray) -> String? { guard array.dataType == .float32 else { return nil } let count = array.count let floatPointer = array.dataPointer.bindMemory(to: Float32.self, capacity: count) let values = Array(UnsafeBufferPointer(start: floatPointer, count: count)) guard let jsonData = try? JSONSerialization.data(withJSONObject: values) else { return nil } return String(data: jsonData, encoding: .utf8) } // JSON字符串转MLMultiArray func jsonToMultiArray(_ jsonString: String) -> MLMultiArray? { guard let jsonData = jsonString.data(using: .utf8), let values = try? JSONSerialization.jsonObject(with: jsonData) as? [Float32] else { return nil } guard let array = try? MLMultiArray(shape: [NSNumber(value: values.count)], dataType: .float32) else { return nil } let floatPointer = array.dataPointer.bindMemory(to: Float32.self, capacity: values.count) for (index, value) in values.enumerated() { floatPointer[index] = value } return array }
读写时直接调用上述方法即可,存储到String类型的Core Data属性中。
内容的提问来源于stack exchange,提问作者MazzzyStar
相关产品推荐
相关产品推荐

