在核心数据中检索/保存自定义对象时出错

Error with retrieve / save custom object in core data

我开发了一个 ios 应用程序,允许用户编辑乐谱 sheet 现在我想实现数据持久性以防止丢弃更改。

阅读 ios 文档,我注意到存在改进数据持久性的不同方法,我相信对我的应用程序来说最好的方法是 Core Data。 考虑到我的应用程序使用了很多自定义对象,我遇到了很多问题。

我正在尝试使用核心数据来保存一个实体,称为分数 sheet,由两个属性组成:

根据文档和其他 q/a 我决定在模型上使用 Trasformable 类型:

所以我声明了一个通用的 class 用作分数属性的转换器:

public class NSSecureCodingValueTransformer<T: NSSecureCoding & NSObject>: ValueTransformer {
  public override class func transformedValueClass() -> AnyClass { T.self }
  public override class func allowsReverseTransformation() -> Bool { true }

  public override func transformedValue(_ value: Any?) -> Any? {
    guard let value = value as? T else { return nil }
    return try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
  }

  public override func reverseTransformedValue(_ value: Any?) -> Any? {
    guard let data = value as? NSData else { return nil }
    let result = try? NSKeyedUnarchiver.unarchivedObject(
      ofClass: T.self,
      from: data as Data
    )
    return result
  }

  /// The name of this transformer. This is the name used to register the transformer using `ValueTransformer.setValueTransformer(_:forName:)`
  public static var transformerName: NSValueTransformerName {
    let className = NSStringFromClass(T.self)
    return NSValueTransformerName("DHC\(className)ValueTransformer")
  }

  /// Registers the transformer by calling `ValueTransformer.setValueTransformer(_:forName:)`.
  public static func registerTransformer() {
    let transformer = NSSecureCodingValueTransformer<T>()
    ValueTransformer.setValueTransformer(transformer, forName: transformerName)
  }
}

以这种方式使用 DHCMeasureValueTransformer 作为 DataModel 文件中的转换器。 问题是,当我保存时,没有发生错误,但是当我为重新启动应用程序获取数据时,我只能获取分数的名称 sheet,而分数数组是空的,就像没有元素一样放在里面(很明显,在保存之前,我尝试打印数组内容,证明我正在使用一个非空数组)

保存代码如下:

static func saveContext() {
        let context = getContext()
        do {
            try context.save()
        } catch {
            print("error during the save.")
        }
    }

这里是实体对象的两个classes的代码:


// DataClass
@objc(ScoreSheet)
public class ScoreSheet: NSManagedObject {
    static var supportsSecureCoding: Bool {
        return true
    }
}

//DataProperties
extension ScoreSheet {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<ScoreSheet> {
        return NSFetchRequest<ScoreSheet>(entityName: "ScoreSheet")
    }

    @NSManaged public var name: String
    @NSManaged public var score: [Measure]
}

Clearly Measure class 实现 NSSecureCoding 和解码和编码对象的方法。

这是措施 class 实施:


import Foundation


class Measure: NSObject, NSCoding, NSSecureCoding {
    
    var elements : [ScoreElement] = []
    var timeSig : TimeSignature
    var clef : Clef
    static var supportsSecureCoding = true
    
    init(time : TimeSignature, clef : Clef) {
      self.timeSig = time
      self.clef = clef
    }
    
    func encode(with encoder: NSCoder) {
        encoder.encode(self.elements, forKey: "elements")
        encoder.encode(self.timeSig, forKey: "timeSig")
        encoder.encode(self.clef, forKey: "clef")
        
    }
    
    required convenience init? (coder decoder: NSCoder) {
        let elements = decoder.decodeObject(forKey: "elements") as! [ScoreElement]
        let timeSig = decoder.decodeObject(forKey: "timeSig") as! TimeSignature
        let clef = decoder.decodeObject(forKey: "clef") as! Clef
        
        self.init(time: timeSig, clef: clef)
        self.elements = elements
    }
    
  
}

我不确定出了什么问题,但有几处问题需要修复,可能会影响您的结果。

首先,计算出的转换器名称与您尝试使用的转换器名称不同。当这一行执行时,TMeasure,

let className = NSStringFromClass(T.self)

然后 className 会变成 MyProjectName.Measure。计算出的转换器名称最终类似于 NSValueTransformerName(_rawValue: DHCMyProjectName.MeasureValueTransformer),这与您在数据模型中使用的名称不匹配。所有这些都意味着您的变压器未被使用。

但这可能并不重要,因为如果 Measure 符合 NSSecureCodingMeasure 的所有属性(ScoreElementTimeSignatureClef) 符合NSSecureCoding(这似乎是因为你的代码没有抛出异常),然后你不根本不需要自定义转换器。如果一个可转换的 属性 类型符合 NSSecureCoding 那么 Core Data 将自动使用 NSSecureCoding。除非出于某种原因不想或不能符合 NSSecureCoding,否则您不需要自定义转换器。因此,您的变压器未被使用并不重要。

至于为什么 Measure 没有在 encode/decode 过程中幸存下来,我不知道,但你可以通过消除不必要的 encode/decode 的干扰来帮助解决问题class。我还建议在 encode(with:)init(coder:) 方法中的 Measure 中放置一个断点。您应该在保存和获取数据时遇到这些断点。