swift 如何使用NSKeyedUnarchiver取消归档MLMultiArray?

46qrfjad  于 2022-12-21  发布在  Swift
关注(0)|答案(1)|浏览(259)

我想将从CoreML预测的MLMultiArray数据存储到本地文件中,因为Core Data不支持这种类型,所以我试用了NSKeyedUnarchiver,因为MLMultiArray符合NSObject
但是,此对象可以是encode并成功保存,但不能是正确的decode

error is: Error Domain=NSCocoaErrorDomain Code=4864 "value for key 'embedding' was of unexpected class 'MLMultiArray' (0x1e35d84b8) [/System/Library/Frameworks/CoreML.framework].
Allowed classes are:
 {(
    "'CoreMLApp.Embedding' (0x1002a53f8) [/private/var/containers/Bundle/Application/C86A850C-27F6-43CB-82AF-9223/CoreMLApp.app]",
    "'NSArray' (0x1e3380b50) [/System/Library/Frameworks/CoreFoundation.framework]"
)}" UserInfo={NSDebugDescription=value for key 'embedding' was of unexpected class 'MLMultiArray' (0x1e35d84b8)

下面是我的对象:

class Embedding: NSObject, NSSecureCoding {
    static var supportsSecureCoding: Bool = true
  
    var id: String?
    var embedding: MLMultiArray?
    
    init(id: String, embedding: MLMultiArray) {
        self.id = id
        self.embedding = embedding
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.id, forKey: "id")
        aCoder.encode(self.embedding, forKey: "embedding")
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.id = aDecoder.decodeObject(forKey: "id") as? String
        self.embedding = aDecoder.decodeObject(forKey: "embedding") as? MLMultiArray
    }
}

编解码部分:

private func saveEmbeddingsData(embeddings: [Embedding], fileName: String) -> Bool {
        let filePath = self.getDocumentsDirectory().appendingPathComponent(fileName)
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: embeddings, requiringSecureCoding: true)
            try data.write(to: filePath)
            return true
        } catch {
            print("error is: \(error.localizedDescription)")
        }
        return false
    }

    private func loadEmbeddingsData(fileName: String) -> [Embedding]? {
        let filePath = self.getDocumentsDirectory().appendingPathComponent(fileName)
        do {
            let data = try Data(contentsOf: filePath)
            let embeddings = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClasses: [Embedding.self], from: data) as? [Embedding]
            return embeddings
        } catch {
            print("error is: \(String(describing: error))")
        }
        return nil
    }
    
    private func getDocumentsDirectory() -> URL {
        let arrayPaths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return arrayPaths[0]
    }

我尝试使用NSCoding而不是NSSecureCoding,但是所有NSKeyedUnarchiver.unarchiveXXX方法都输出错误,要求数据符合NSSecureCoding

nr9pn0ug

nr9pn0ug1#

NSSecureCoding和NSKeyedUnarchiver要求你指定你所期望的类的类型。整个想法是为了避免有人偷偷进入意想不到的、狡猾的值,这可能会危及你代码的安全性。
由于存档的是Embedding数组,因此存档文件还包含MLMultiArray类型的值。这意味着在取消存档数据时,必须指定预期的NSArrayEmbeddingMLMultiArray类型的值。
由于使用的是NSKeyedUnarchiver.unarchivedArrayOfObjects,因此不需要显式指定NSArray,但需要将[Embedding.self, MLMultiArray.self]传递给classes参数。
错误消息是一个重要提示。

Allowed classes are:
 {(
    "'CoreMLApp.Embedding' (0x1002a53f8) [/private/var/containers/Bundle/Application/C86A850C-27F6-43CB-82AF-9223/CoreMLApp.app]",
    "'NSArray' (0x1e3380b50) [/System/Library/Frameworks/CoreFoundation.framework]"
)}" UserInfo={NSDebugDescription=value for key 'embedding' was of unexpected class 'MLMultiArray' (0x1e35d84b8)

这是告诉你只有EmbeddingNSArray是允许的,因为这是你告诉NSKeyedUnarchiver.unarchivedArrayOfObjects所期望的,并且错误告诉你它在归档文件中找到了MLMultiArray,但是它不在允许的类列表中。
通过更改以下行可以解决此问题:

let embeddings = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClasses: [Embedding.self], from: data) as? [Embedding]

致:

let embeddings = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClasses: [Embedding.self, MLMultiArray.self], from: data) as? [Embedding]

对我来说,NSSecureCoding最大的问题是它破坏了封装。你的代码需要知道Embedding类的内部细节。一个解决方案是向Embedding类添加helper方法来执行存档和解存档,这样其他代码就不需要知道需要指定什么类。

相关问题