As I understand, the use of #try/#catch blocks is discouraged, because exceptions should only be thrown at unrecoverable, catastrophic errors (refer to this discussion with a nice answer by #bbum: Exception Handeling in iOS).
So I looked through my code and found a #try/#catch block that I don't know how to get rid of:
NSData *fileData = [NSData dataWithContentsOfFile: ....];
NSDictionary *dictionary;
#try {
dictionary = [NSKeyedUnarchiver unarchiveObjectWithData: fileData];
}
#catch (NSException *exception) {
//....
}
#finally {
//...
}
The problem is that (as stated in the documentation) +unarchiveObjectWithData: raises an NSInvalidArchiveOperationException if the NSData doesn't contain a valid archive.
Since the data is provided by a file the user chose, it is not guaranteed that it contains a valid archive, and thus the application would crash if a incorrect file was chosen.
Now two questions:
Why doesnt +unarchiveObjectWithData: just return nil (Edit: and an NSError**) if the archive is not valid (this doesn't seem to qualify as a catastrophic or unrecoverable error).
Is the pattern above correct (using #try)? I have found no method that lets us check if the data contains a valid archive beforehand and have found no possibility to handle this case using the delegate protocol. Antyhing I overlooked?
Note that the code above of course works, I just wonder if its the best practice.
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.
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.
Regarding your questions:
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).
Conclusion:
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.
Related
In Apple's Concurrency Programming Guide the NSOperation subclass examples (both non-concurrent and concurrent varieties) use exception handling and I'm wondering why they are encouraging this style within operations.
Listing 2-4 Responding to a cancellation request
- (void)main {
#try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
}
#catch(...) {
// Do not rethrow exceptions.
}
}
My understanding is that generally exception handling is not a common practice in Objective-C code - exceptions are essentially programmer errors and should cause the app to crash whereas unexpected inputs are best handled by NSError. (My possibly misinformed understanding comes from things like this and this)
I'm wondering if NSOperations present a particular situation in which exception handling is important, or if this is more the preferred style of the particular author of that guide.
As a side note, some of the NSOperation example code follows this style, other examples do not. Most high-visibility OSS does not use exceptions (AFNetworking, for example).
Your understanding is correct - NSError (or similar) should be used to convey error information, rather than exceptions. Most Objective-C code is not exception-safe and will at the very least leak resources. As a general rule, never let your code leak an exception into anyone else's code - whether Apple's or a 3rd parties. Some 3rd party frameworks may explicitly indicate they are exception safe, but it's rare.
By that principle you can see why you should have a catch-all exception handler in your main method regardless. But there's actually another reason: your operation will be run on a dedicated thread. Exceptions thrown from your operation will propagate up the stack, but no further. The logical caller or owner of the operation won't get them, as they're running on a different thread (or not at all). So leaked exceptions will either kill your whole program, or be swallowed silently with no other indication. Your program may then get stuck in a weird state - since you didn't realise an error occurred, you may continue waiting for the result of your operation that will never arrive.
Additionally, Apple has a section in the Concurrency Programming Guide where they talk about Handling Errors and Exceptions. Their first point on "discrete entities" is alluding to what I said in the previous paragraph:
Handling Errors and Exceptions
Because operations are essentially
discrete entities inside your application, they are responsible for
handling any errors or exceptions that arise. In OS X v10.6 and later,
the default start method provided by the NSOperation class does not
catch exceptions. (In OS X v10.5, the start method does catch and
suppress exceptions.) Your own code should always catch and suppress
exceptions directly. It should also check error codes and notify the
appropriate parts of your application as needed. And if you replace
the start method, you must similarly catch any exceptions in your
custom implementation to prevent them from leaving the scope of the
underlying thread.
Among the types of error situations you should be prepared to handle
are the following:
Check and handle UNIX errno-style error codes.
Check explicit error
codes returned by methods and functions.
Catch exceptions thrown by
your own code or by other system frameworks.
Catch exceptions thrown
by the NSOperation class itself, which throws exceptions in the
following situations:
When the operation is not ready to execute but
its start method is called
When the operation is executing or finished
(possibly because it was canceled) and its start method is called
again
When you try to add a completion block to an operation that is
already executing or finished
When you try to retrieve the result of
an NSInvocationOperation object that was canceled
If your custom code
does encounter an exception or error, you should take whatever steps
are needed to propagate that error to the rest of your application.
The NSOperation class does not provide explicit methods for passing
along error result codes or exceptions to other parts of your
application. Therefore, if such information is important to your
application, you must provide the necessary code.
I think this post and the accompanying answer elaborates very well on the general exception- vs. no exception handling topic!
It is unsafe to throw exceptions in circumstances where resources are
not automatically managed. This is the case of the Cocoa framework
(and neighbor frameworks), as they use manual reference counting.
If you throw an exception, any release call you skip over by unwinding
the stack will result in a leak. This should limit you tothrowing only
if you're certain that you're not going to recover since all resources
are returned to the OS when a process quits.
Unfortunately, NSRunLoops tend to catch all exceptions that propagate
to them, so if you throw during an event, you'll resume to the next
event. This is, obviously, very bad. Therefore, it's better that you
simply don't throw.
This problem is diminished if you use garbage-collected Objective-C,
as any resource represented by an Objective-C object will be properly
released. However, C resources (such as file descriptors or
malloc-allocated memory) that are not wrapped in an Objective-C object
will still leak.
So, all in all, don't throw.
The Cocoa API has several workarounds to this, as you mentioned.
Returning nil and the NSError** pattern are two of them.
I am creating an application on iPhone where I am taking code written and Java and translating into Objective-C, as the app must work similarly in both Android and iPhone. Now problems of course are there are certain areas where the languages differ quite a bit, and can be difficult to implement just right.
One of the areas where it is of course different is you cannot create NSMutableArrays or NSMutableDictionaries which only accepts objects of a certain type. So when I am creating the getters and setters for these objects, I have been doing this, example below:
-(void)setPerson:(NSMutableArray *)personList_p
{
BOOL bAllowed = YES;
for(int i = 0; i < [personList_p count]; i++)
{
if(![[personList_p objectAtIndex:i] isKindOfClass:[Person class]])
{
bAllowed = NO;
break;
}
else
{
bAllowed = YES;
}
}
if(bAllowed)
{
personList_i = personList_p;
}
else
{
//Raise error here
}
}
Now originally I was using NSRaise Exception, but after doing reading up on it, it recommends against this as exception handling is resource intensive and should not be used for this type of situation. So NSError I read was a good solution, so I was reading up on this guide
http://www.cimgf.com/2008/04/04/cocoa-tutorial-using-nserror-to-great-effect/
My question is two fold, if I decide to use NSError should, is it best practice to declare the NSError object in the method, i.e like this
-(void)setPerson:(NSMutableArray *)personList_p : (NSError **)error
Or is it okay just to declare the error within the else statement above, like this
NSError *error = nil;
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:#"Invalid object passed into Array, object must be of type Person." forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:#"Invalid Object" code:200 userInfo:details];
NSLog(#"%#", [error localizedDescription]);
Secondly, in the guide I linked above, in the conclusion the author says
In this example, I am checking to see if the error is still nil after
my message call. If it is no longer nil I know that an error occurred
and that I need to display it to the user. Apple provides a built in
method to do this with a call to presentError: on the NSApplication
instance
and he uses this line of code here
[NSApp presentError:error];
This does not work for me, I do not get an autocomplete option with this code, just says use of undeclared identifier. Am I missing something obvious here?
Also, just to be sure, is the method I am using, the best method to go about ensuring the user sends in an array with the correct object types? Or is there another method which could be used which would achieve the same thing but in a more efficient manner.
Thanks in advance!!
EDIT:
Additional question, would it be such a bad idea to use NSException, as the problem with NSError is the app carries on, I am considering having it so if they do pass in an array with an invalid object, that it does just cause the application to crash and raise an exception. Is there any major reason why I shouldn't do this??
Now originally I was using NSRaise Exception, but after doing reading
up on it, it recommends against this as exception handling is resource
intensive and should not be used for this type of situation. So
NSError I read was a good solution, so I was reading up on this guide
He does not actually say that raising exceptions is inherently bad or resource intensive, he argues that #try/catch is the wrong way to go about things. That, and Cocoa applications (even more so I'm Cocoa-Touch) should refrain from throwing exceptions whenever possible. Exceptions indicate undefined behavior, not simple errors.
Secondly, in the guide I linked above, in the conclusion the author...
uses this line of code here
[NSApp presentError:error];
This does not work for me, I do not get an autocomplete option with
this code, just says use of undeclared identifier. Am I missing
something obvious here?
That is because NSApp is a concept only accessible to Cocoa(or Mac) applications. It represents a pointer to "the application itself", and has some pretty neat functions associated with it. Of course, iOS has no parallel concept, so errors are traditionally presented in a UIAlertView.
Additional question, would it be such a bad idea to use NSException,
as the problem with NSError is the app carries on, I am considering
having it so if they do pass in an array with an invalid object, that
it does just cause the application to crash and raise an exception. Is
there any major reason why I shouldn't do this??
It is generally a bad idea to use NSException for anything other than extremely serious logic (such as when a call is made to an index out of the bounds of an NSArray, or when a UITableView's data source happens to be out of sync with what the table demands). If you absolutely must stop program execution, it's much cleaner to use NSAssert() to throw conditional exceptions. And even then, setters are not the place to be throwing exceptions. It would be far easier to simply return nil when the condition fails, then have the caller check for such an outcome and handle it appropriately.
I have seen some iOS developpers using code like this :
- (void)setupWebView:(UIWebView**)aWebView {
UIWebView *webview = [[UIWebView alloc] init];
.....
if (*aWebView) {
[*aWebView release];
}
*aWebView = webview;
}
Do you know what'is this mean and why we use this ? thanks
- (void)setupWebView:(UIWebView**)aWebView {
That is awful. You should never have a method that returns void, but sets an argument by reference unless:
• there are multiple arguments set
• the method is prefixed with get
That method should simply return the created instance directly. And this just makes it worse -- is flat out wrong:
if (*aWebView) {
[*aWebView release];
}
*aWebView = webview;
it breaks encapsulation; what if the caller passed a reference to an iVar slot. Now you have the callee managing the callers memory which is both horrible practice and quite likely crashy (in the face of concurrency, for example).
it'll crash if aWebView is NULL; crash on the assignment, specifically.
if aWebView refers to an iVar slot, it bypasses any possible property use (a different way of breaking encapsulation).
It is a method to initialize a pointer. The first line allocates the object. The if statement makes sure that the passed in pointer-to-a-pointer is not already allocated, if it is it releases it. then it sets the referenced pointer to the newly allocated object.
The answer by #bbum is probably correct, but leaves out one aspect to the question that I see there. There are many examples in Foundation which use pointer-pointers in the method signature, so you can say it is a common pattern. And those are probably not a beginners mistake.
Most of these examples are similar in that they fall into one category: the API tries to avoid the usages of exceptions, and instead use NSError for failures. But because the return value is used for a BOOL that signals success, an NSError pointer-pointer is used as output parameter. Only in the probably rare error case an NSError object is created, which can contain error code and error descriptions, and localized descriptions and possibly even more information (like an array of multiple errors in the case of bulk operations). So the main success case is efficient, and the error case has some power to communicate what went wrong, without resorting to exceptions. That is the justification behind these signatures as I understand it.
You can find examples of this usage in both NSFileManager and NSManagedObjectContext.
One might be tempted to use pointer-pointers in other cases where you want multiple return values and an array does not make sense (e.g. because the values are not of same type), but as #bbum said, it is likely better to look hard for alternatives.
I find myself writing code like this to achieve exception safe code:
Container* container = [Container new];
#try {
while(someCondition) {
ElementType* value = [someObject createObjectFromStorage];
[container add:value]; // container retains object
[value release];
}
[_container release];
_container = [container retain];
} #finally {
[container release];
}
Is there some other, and more succinct pattern to follow in Objective-C?
If you're just looking to make sure you've released your objects, autorelease is probably sufficient. You might also look into the new Automatic Reference Counting option in Xcode 4.2.
Objective-C does not, in general, lend itself to RAII because all Objective-C objects are allocated on the heap. This means that the lifetime of objects is not explicitly tied to any particular stack frame, and thus you cannot rely on the object being deallocated at the end of the method that allocated it.
You should also be aware that the Cocoa frameworks use exceptions only to indicate programmer error, not anticipated error conditions. From Apple's "Exception Programming Guide" documentation:
Important: You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
If you have an existing body of code (such as third-party library) that uses exceptions to handle error conditions, you may use the code as-is in your Cocoa application. But you should ensure that any expected runtime exceptions do not escape from these subsystems and end up in the caller’s code. For example, a parsing library might use exceptions internally to indicate problems and enable a quick exit from a parsing state that could be deeply recursive; however, you should take care to catch such exceptions at the top level of the library and translate them into an appropriate return code or state.
In fact, because exceptions are intended to be used only for exceptional cases, by default the newly introduced Automatic Reference Counting will intentionally leak objects when throwing an exception:
The standard Cocoa convention is that exceptions signal programmer error and are not intended to be recovered from. Making code exceptions-safe by default would impose severe runtime and code size penalties on code that typically does not actually care about exceptions safety. Therefore, ARC-generated code leaks by default on exceptions, which is just fine if the process is going to be immediately terminated anyway. Programs which do care about recovering from exceptions should enable the option.
Programming with the Cocoa frameworks will go much better if you adhere to the idioms of that framework. That means using exceptions only for programmer errors and handling anticipated runtime errors with NSError. Most Cocoa programmers never worry about writing exception-safe code, because their code doesn't throw exceptions in the first place. You may do well to follow suit.
Autorelease is the standard pattern.
Container* container = [[Container new] autorelease];
while(someCondition) {
ElementType* value = [someObject createObjectFromStorage];
[container add:value]; // container retains object
[value release];
}
[_container release];
_container = [container retain];
This only applies to memory management, however; it's not a full replacement for RAII. There's no commonly-used pattern in Objective-C that completely replaces RAII, although you could create such a pattern in your own codebase using blocks (or maybe __attribute__((cleanup))).
While we're talking about patterns, by the way… the general pattern is that any method that doesn't start with the word new, alloc, copy, or mutableCopy returns an autoreleased object. If you changed your hypothetical method to -objectFromStorage and made it follow that pattern, your loop would be even more concise:
while(someCondition) {
[container add:[someObject objectFromStorage]];
}
Im newish to objective-c and am starting to wonder what is the common/standard/proper way for handling and catching errors?
It seems like it might be possible to use NSError to do this, is that a good idea or a hijack of cocoa?
I'm pretty sure that's what the NSError class is there to do - give details about errors. The most common pattern you'll see is a method that takes a pointer to an NSError object, as in:
- (id)doSomethingWithArgument:(id)arg error:(NSError **)error
The method returns some value (or possibly nil) for the result of doing something, but if the call failed will place an NSError object at the pointer passed with details about the failure. Your documentation is responsible for specifying what gets returned if the method does encounter an error.
The other method that comes to mind is the #throw-#catch block; however, in Objective-C #throwing an exception can be rather computationally expensive, and it's usually only recommended to do so in truly exceptional situations.
Edit: wow, turns out a lot of people have really strong opinions about #throwing exceptions. To sum up the (quite helpful) commentary on the issue:
Throwing exceptions should most often deal with programmer error (situations that should never happen, and the like); exceptions should not be used for ordinary error handling. Instead, use the error method demonstrated above or post instances of NSNotification.
If you do wind up making extensive use of #throw/#catch blocks, be very careful about the logic surrounding them. Objective-C provides a lot of ways to detach methods to run in other threads, or delay execution, etc. Be very careful to account for all those possibilities when you write your code.
Finally, another very valid point:
If you do use the error object passed to a method, the return value should indicate it. Don't try to do both (return a partially valid object and set the error object).