Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Less verbose way to log errors in Objective-C

Many Cocoa methods take an optional NSError ** argument that they use to report errors. Frequently, I find myself using such methods even in circumstances where the only way that an error could occur is through programming error on my part, rather than unexpected runtime conditions. As such, I don't want to write any error-handling code that will do any user-visible things. All I really want to do upon error is log the error (and perhaps crash), while keeping my code as concise and readable as possible.

The trouble is that the 'keep the code concise' and 'log the error' objectives are in tension with each other. I frequently have a choice between these two approaches, both of which I dislike:

1. Pass NULL for the error pointer argument.

[managedObjectContext save:NULL];
  • Advantages: Succinct, readable, and makes clear that errors are not expected. Perfectly fine as long as I was correct in my belief that an error here is logically impossible.
  • Disadvantages: If I've screwed up and an error does happen, it won't be logged and my debugging will be harder. In some circumstances, I might not even notice the error happened.

2. Pass an NSError ** and log the resulting error, with the same boilerplate code, every single time.

NSError *error;
[managedObjectContext save:&error];
if (error) {
    NSLog(@"Error while saving: %@", error);
}
  • Advantages: Error don't pass silently - I'm alerted to them and given debugging information.
  • Disadvantages: It's horribly verbose. It's slower to write, slower to read, and when it's nested within some levels of indentation already, I feel that it makes the code less readable. Routinely doing this just for logging errors, and getting used to skipping over the boilerplate while reading, also makes me sometimes fail to notice when some code I'm reading actually has significant error handling blocks for errors that are expected to happen at runtime.

Coming from a background of languages like Python, Java, PHP and Javascript, I find it kind of cumbersome to have to write 4 extra lines of boilerplate to get notified of the kind of errors that, in the languages I'm used to, I'd find out about through an exception or warning without ever having to write any code that explicitly checks for errors.

What I'd ideally like is some cunning hack that I can use to automatically log the errors created by these methods without needing to write the boilerplate on every method call, thus giving me the benefits of both the lazy NULL-passing approach and the error-logging boilerplate. In other words, I'd like to write this:

[managedObjectContext save:&magicAutologgingError];

and know that if the method created an NSError, it would somehow, magically, be logged.

I'm not too sure how to go about this. I considered using an NSError subclass that logs itself upon dealloc, but realised that since I'm not responsible for instantiating the error objects that Cocoa's methods create, my subclass wouldn't be used anyway. I considered using method swizzling to make all NSErrors log themselves on dealloc like this, but I'm not sure if that would actually be desirable. I thought about using some sort of observer class that watches a given constant space in memory that I could use for the NSError pointers that I want to log, but as far as I know there's no way to do anything like KVO for observing arbitrary spaces in memory, so I can't see an approach for implementing this other than having a thread that repeatedly checks for errors to log.

Can anyone see a way to achieve this?

like image 673
Mark Amery Avatar asked Sep 28 '13 11:09

Mark Amery


People also ask

How do you handle errors in Objective-C?

In Objective-C programming, error handling is provided with NSError class available in Foundation framework. An NSError object encapsulates richer and more extensible error information than is possible using only an error code or error string.

Where does NSLog write to?

NSLog outputs messages to the Apple System Log facility or to the Console app (usually prefixed with the time and the process id). Many of the system frameworks use NSLog for logging exceptions and errors, but there is no requirement to restrict its usage to those purposes.

How do you throw an exception in Objective-C?

Xcode recognizes @throw statements as function exit points, like return statements. Using the @throw syntax avoids erroneous "Control may reach end of non-void function" warnings that you may get from [NSException raise:…] . Also, @throw can be used to throw objects that are not of class NSException.

What is Objective-C in iOS?

Objective-C is the primary programming language you use when writing software for OS X and iOS. It's a superset of the C programming language and provides object-oriented capabilities and a dynamic runtime.


1 Answers

Just create a wrapper function (or category method) which does what you want it to:

bool MONSaveManagedObjectContext(NSManagedObjectContext * pContext) {
 NSError * error = nil;
 bool result = [pContext save:&error];
 if (!result && nil != error) {
  // handle  the error how you like -- may be different in debug/release
  NSLog(@"Error while saving: %@", error);
 }
 return result;
}

and call that instead. Or you might prefer to make error handling separate:

void MONCheckError(NSError * pError, NSString * pMessage) {
 if (nil != pError) {
  // handle  the error how you like -- may be different in debug/release
  NSLog(@"%@: %@", pMessage, pError);
 }
}

...

NSError * outError = nil;
bool result = [managedObjectContext save:&outError];
MONCheckError(outError, @"Error while saving");

Always be on the lookout for duplicate code :)


I considered using method swizzling to make all NSErrors log themselves on dealloc like this, but I'm not sure if that would actually be desirable.

It's not desirable.

like image 194
justin Avatar answered Oct 09 '22 19:10

justin