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.
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 typeMLMultiArray. This means that when you unarchive the data you must specify that you expect values of type,NSArray,Embedding, andMLMultiArray.Since you are using
NSKeyedUnarchiver.unarchivedArrayOfObjectsyou don't need to explicitly specifyNSArray. But you do need to pass[Embedding.self, MLMultiArray.self]to theclassesparameter.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
EmbeddingandNSArrayare allowed since that is all you toldNSKeyedUnarchiver.unarchivedArrayOfObjectsto expect. And the error tells you that it foundMLMultiArrayin the archive but it was not in the list of allowed classes.This is resolved by changing the line:
to:
To me, the biggest issue with NSSecureCoding is that it breaks encapsulation. Your code to unarchive needs to know the internal details of the
Embeddingclass. One solution to this may be to add helper methods to theEmbeddingclass to do the archiving and unarchiving so other code doesn't need to know what classes need to be specified.