I want to store MLMultiArray
data predicted from CoreML into local file, because Core Data
does not support for this type, I tried out NSKeyedUnarchiver
, as MLMultiArray
conforms to NSObject
.
However, this Object can be encode
& save successfully but can not be decode
correctly.
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)
Below is my Object:
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
}
}
The encode/decode part:
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]
}
I've tried with NSCoding
rather than NSSecureCoding
, but all NSKeyedUnarchiver.unarchiveXXX
methods output errors that require the data to conform NSSecureCoding
.
CodePudding user response:
NSSecureCoding and NSKeyedUnarchiver require that you specify the types of classes you are expecting. The whole idea is to avoid someone sneaking in unexpected, crafty values that could compromise the security of your code.
Since you archive an array of Embedding
, the archive also contains values of type MLMultiArray
. This means that when you unarchive the data you must specify that you expect values of type, NSArray
, Embedding
, and MLMultiArray
.
Since you are using NSKeyedUnarchiver.unarchivedArrayOfObjects
you don't need to explicitly specify NSArray
. But you do need to pass [Embedding.self, MLMultiArray.self]
to the classes
parameter.
The error message is the big hint.
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)
This is telling you that only Embedding
and NSArray
are allowed since that is all you told NSKeyedUnarchiver.unarchivedArrayOfObjects
to expect. And the error tells you that it found MLMultiArray
in the archive but it was not in the list of allowed classes.
This is resolved by changing the line:
let embeddings = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClasses: [Embedding.self], from: data) as? [Embedding]
to:
let embeddings = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClasses: [Embedding.self, MLMultiArray.self], from: data) as? [Embedding]
To me, the biggest issue with NSSecureCoding is that it breaks encapsulation. Your code to unarchive needs to know the internal details of the Embedding
class. One solution to this may be to add helper methods to the Embedding
class to do the archiving and unarchiving so other code doesn't need to know what classes need to be specified.