As I understand, the use of @try/@catch
blocks is discouraged, because exceptions
should only be thrown at unrecoverable, catastrophic errors (refer to
NSKeyedArchiver
is built by Apple. They control the code that is performed while unarchiveObjectWithData:
executes so they also control resource management during exception handling (which is the source of trouble behind exceptions in Objective-C).
If they can guarantee that in between your call to unarchiveObjectWithData:
and the point in code where they raise the exception is no foreign code (neither third party nor your app's code) it is in theory possible to safely use an exception, as long as calling code takes care of cleaning up correctly.
The problem is that this assumption might not be the case: It is common to use NSKeyedArchiver
to serialize custom objects. Usually the custom class implements initWithCoder:
to read the classes data (by using the archiver's methods like decodeObjectForKey:
).
If the archiver throws an exception in one of these methods there's no way to fix resource handling for the archiver. The exception will be thrown through the custom object's initWithCoder:
. The archiver does not know if there's more stuff to clean up than the deserialized objects. So in this scenario the occurrence of the exception means that the process is in a dangerous state and unwanted behavior may result.
Why doesn't [NSKeyedArchiver use proper Cocoa error handling]?
Only the Apple engineers who built the archiver know. My guess is that exception handling and keyed archiving were built at roughly the same time (around 2001) and at that point it wasn't yet clear that exception handling would never be a first class citizen in Objective-C.
Is the @try pattern correct?
With the limitation of the caveats described above it is correct. If Apple's code handles the exception cases properly and your own serialization code does the same the @try pattern might be correct.
It is very difficult to achieve full correctness, though. You'd have to make sure all executed code is aware of the exceptions and does cleanup correctly.
ARC, for instance, does no exception cleanup for local variables and temporary objects by default (you would have to enable -fobjc-arc-exceptions
to do this).
Also, there's no documentation on exception safety of the accessors of @synthesized properties (when atomic
they might leak a lock).
There are a myriad of subtle ways of how exceptions can break stuff. It is difficult and requires in depth knowledge of the implementation of all involved parts to build exception safe code in Objective-C.
All this leads to the conclusion. If you want to handle errors gracefully while loading possibly corrupted archives and continue normal execution afterwards: Do not use NSKeyedArchiver
.
There was a new method added in iOS 9 to NSKeyedUnarchiver that now returns an error:
Swift:
public class func unarchiveTopLevelObjectWithData(data: NSData) throws -> AnyObject?
Objective-C:
+ (nullable id)unarchiveTopLevelObjectWithData:(NSData *)data error:(NSError **)error;
However, this is not backwards compatible with previous versions of iOS, so you will need to check for framework availability.