NSCoding
defines it as Optional:
init?(coder aDecoder: NSCoder)
So it is certainly possible to detect errors.
90% of "why does Swift...?" questions can be answered with "because Cocoa." Cocoa does not define initWithCoder:
as returning an error, so it does not translate to throws
in Swift. There would be no way to cleanly bridge it to existing code. (NSCoding
goes back NeXTSTEP. We've built a lot of software without returning an NSError
there. Doesn't mean it might not be nice sometimes, but "couldn't init" has traditionally been enough.)
Check for nil
. That means that something failed. That is all the information that is provided.
I've never in practice had to check too deeply that the entire object graph was correct. If it isn't, you're incredibly likely to get other errors anyway, and remember that NSKeyedUnarchiver
will raise an ObjC exception (!!!) if it fails to decode. Unless you wrap this in an ObjC @catch
, you're going to crash anyway. (And yes, that's pretty crazy, but still true.)
But if I wanted to be extremely careful and make sure that things I expected to be in the archive were really in the archive (even if they were nil
), I might do it this way (untested; it compiles but I haven't made sure it really works):
import Foundation
enum DecodeError: ErrorType {
case MissingProperty(String)
case MalformedProperty(String)
}
extension NSCoder {
func encodeOptionalObject(obj: AnyObject?, forKey key: String) {
let data = obj.map{ NSKeyedArchiver.archivedDataWithRootObject($0) } ?? NSData()
self.encodeObject(data, forKey: key)
}
func decodeRequiredOptionalObjectForKey(key: String) throws -> AnyObject? {
guard let any = self.decodeObjectForKey(key) else {
throw DecodeError.MissingProperty(key)
}
guard let data = any as? NSData else {
throw DecodeError.MalformedProperty(key)
}
if data.length == 0 {
return nil // Found nil
}
// But remember, this will raise an ObjC exception if it's malformed data!
guard let prop = NSKeyedUnarchiver.unarchiveObjectWithData(data) else {
throw DecodeError.MalformedProperty(key)
}
return prop
}
}
class MyClass: NSObject, NSCoding {
static let propertyKey = "property"
let property: String?
init(property: String?) {
self.property = property
}
required init?(coder aDecoder: NSCoder) {
do {
property = try aDecoder.decodeRequiredOptionalObjectForKey(MyClass.propertyKey) as? String
} catch {
// do something with error if you want
property = nil
super.init()
return nil
}
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeOptionalObject(property, forKey: MyClass.propertyKey)
}
}
As I said, I've never actually done this in a Cocoa program. If anything were really corrupted in the archive, you're almost certainly going to wind up raising an ObjC exception, so all this error checking is likely overkill. But it does let you distinguish between "nil" and "not in the archive." I just encode the property individually as an NSData
and then encode the NSData
. If it's nil
, I encode an empty NSData
.