I am trying to catch the `NSKeyedUnarchiver` unarchiving exception `NSInvalidUnarchiveOperationException` where an unknown class is being decoded securely via `NSSecureCoding` protocol.
The solution I am using is based on a related [NSKeyedUnarchiverDelegate SO post][SONSKeyedUnarchiverDelegate] by implementing the delegate protocol `NSKeyedUnarchiverDelegate` so I can listen and respond to the exception via `unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:)`. However, this delegate method doesn't seem to be called when unknown class is encountered during decoding.
Here's the code snippet that I use for securely unarchiving an array object.
func securelyUnarchiveArrayOfCustomObject(from url: URL, for key: String) -> [MyCustomClass]? {
guard let data = try? Data(contentsOf: url) else {
os_log("Unable to locate data at given url.path: %@", log: OSLog.default, type: .error, url.path)
return nil
}
let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
let delegate = UnarchiverDelegate() // Prevents `NSInvalidUnarchiveOperationException` crash
unarchiver.delegate = delegate
unarchiver.requiresSecureCoding = true // Prevents object substitution attack
let allowedClasses = [NSArray.self] // Will decode without problem if using [NSArray.self, MyCustomClass.self]
let decodedObject = unarchiver.decodeObject(of: allowedClasses, forKey: key)
let images = decodedObject as! [ImageWithCaption]?
unarchiver.finishDecoding()
return images
}
where my `UnarchiverDelegate` is implemented just like in the original [NSKeyedUnarchiverDelegate SO post][SONSKeyedUnarchiverDelegate] I pointed to. Under my setup, `decodeObject(of: allowedClasses, forKey: key)` doesn't throw an exception, but instead raises a runtime exception:
'NSInvalidUnarchiveOperationException', reason:
'value for key 'NS.objects' was of unexpected class
'MyCustomClassProject.MyCustomClass'. Allowed classes are '{(
NSArray
)}'.'
This is supposedly just the kind of exception that `NSKeyedUnarchiverDelegate`'s `unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:)` should be invoked, based on [its documentation][AppleDocDelegate]:
> Informs the delegate that the class with a given name is not available during decoding.
But in my case, this method isn't invoked with the snippet above (even though other delegate methods, like `unarchiverWillFinish(_:)` or `unarchiver(_:didDecode:)` are invoked normally when decoding doesn't encounter a problem.
Unlike in the original post, I cannot use class function like `decodeTopLevelObjectForKey` where I can handle exception with `try?`, because I need to support secure encoding and decoding with `NSSecureCoding` protocol, like discussed [here][SONSSecureCodingCustomCollection]. This forces me to use `decodeObject(of:forKey)`, which doesn't throw any exception that I can handle, and, again, it doesn't inform my delegate before throwing runtime exception that leads to app crash either.
Under what scenarios is the delegate method `unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:)` actually invoked? How can I listen and react to the `NSInvalidUnarchiveOperationException` under my `NSSecureCoding` setup so I can avoid runtime crash when the decoding isn't successful?
[SONSKeyedUnarchiverDelegate]: http://stackoverflow.com/questions/32904811/swift-only-way-to-prevent-nskeyedunarchiver-decodeobject-crash
[SONSSecureCodingCustomCollection]: http://stackoverflow.com/questions/24376746/nssecurecoding-trouble-with-collections-of-custom-class
[AppleDocDelegate]: https://developer.apple.com/reference/foundation/nskeyedunarchiverdelegate/1409948-unarchiver