My current Prefix.pch file
I have posted and discussed this file a few times but as with all things it has been touched, tweaked, and generally improved upon.
In this article we will discuss the latest iteration of my `Prefix.pch` file. As with anything I post, it is available for you to use as you see fit.
## The File
For those who don’t want to read the entire post, here is the file:
#ifdef DEBUG
#define DLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
#define ALog(...) [[NSAssertionHandler currentHandler] handleFailureInFunction:[NSString stringWithCString:__PRETTY_FUNCTION__ encoding:NSUTF8StringEncoding] file:[NSString stringWithCString:__FILE__ encoding:NSUTF8StringEncoding] lineNumber:__LINE__ description:__VA_ARGS__]
#else
#define DLog(...) do { } while (0)
#ifndef NS_BLOCK_ASSERTIONS
#define NS_BLOCK_ASSERTIONS
#endif
#define ALog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
#endif
#define ZAssert(condition, ...) do { if (!(condition)) { ALog(__VA_ARGS__); }} while(0)
This does not *replace* the Prefix.pch that comes with your project but it does go at the top of every project that I work on. The rest of this post we will review what this does.
## `#ifdef DEBUG`
I normally run my code in one of two modes. Either I am writing and testing the code or I am compiling it to hand over to QA, a user or Apple (btw I consider all three of those to be synonymous when it comes to builds). Therefore the first line is a switch to see if we are in debug mode. I set this value in the build settings of my project. If you look under the “Preprocessor Macros” section you can set the `DEBUG` definition there.
## `#define DLog(…)`
This is the most used macro I have in my `Prefix.pch` file. This is simply `NSLog` as we know it and love it. However I automatically prepend the included macro `__PRETTY_FUNCTION__` so that any log statement that comes out will declare where it is being called from. Personally I hate having to track down which “Fix Me” just spit out to the Console.
The nice thing about `DLog` over `NSLog` is that in the other branch this is a no-op that will be removed by the compiler. Therefore when I do a client build they won’t see the debug statements nor will their build be slowed down by any potential conditional logic around the debug statements.
## `#define ALog(…)`
While I do not use this one very often I do like this one an awful lot. When I am in `DEBUG` mode it will throw an `NSAssertion` when it gets hit. This is similar to a common line of code that I see:
NSAssert(NO, @"It failed");
or if you want to see the variables:
NSAssert2(NO, @"It failed. Value1: %i Value2: %i", value1, value2);
My version does not have the condition before hand and can take any number of parameters.
ALog(@"It failed. Value1: %i Value2: %i", value1, value2);
In addition, when the `DEBUG` flag is not set, this assertion turns into a `NSLog` which remains visible. This allows me to have “This will never happen” calls in my code that will explode when I hit them but will be more polite if a user hits them but not so polite that I am left wondering what happened. They won’t intentionally crash the application but they will leave a fingerprint in the output from the application so that I can discover what happened.
## `NS_BLOCK_ASSERTIONS`
I am of the school of thought that believes you should never throw an assertion in production code. If your code cannot handle that part of your app being hit and survive in some form or another then it is not production ready. Simple as that. Therefore I turn them off in production builds.
However, some other people who write libraries that I depend upon also feel this way. Therefore I need to check to see if it is already defined so that I can avoid a warning about resetting it. Since I have warnings set as errors in my code this is needed.
## `#define ZAssert(condition, …)`
The final gem in this collection is `ZAssert`. This is my personal favorite and it lets me clean up my code very nicely. First, `ZAssert` is a condition check. If you pass the check nothing happens. If you fail the check, bad things happen. This is just like `NSAssert` except for one big difference. When you turn off assertions, `NSAssert` goes away completely. This is not what I want to happen in my code. Instead, when assertions are turned off, I want a failure of this check to turn into an `NSLog`. That is what `ZAssert` does for us. In this way, we can clean up our code very nicely. Picture this very common block of code:
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(@"My save failed: %@\n%@", [error localizedDescription], [error userInfo]);
abort();
}
That is a LOT of code just to perform a save! Blocks of code like this are very common in Objective-C. Unfortunately we can’t roll this up into a `NSAssert` because the entire line of code will disappear when we turn them off.
With `ZAssert` it gets a lot cleaner:
NSError *error = nil;
ZAssert([managedObjectContext save:&error], @"My save failed: %@\n%@", [error localizedDescription], [error userInfo]);
Because ZAssert survives when `NS_BLOCK_ASSERTIONS` is set and simply mutates, we can inline the save directly and make the code a lot easier to read.
# Wrap Up
I am sure this code will evolve. When it has changed significantly I will do an updated post. If anyone else has some nice tips for the `Prefix.pch` then please share them as we all benefit.
# Acknowledgements
First I would like to recognize Fraser Hess who did the original post and showed me what we can do with macros.
Second I would like to point out that the NSAssert line of code above is borrowed heavily from BareBones software. They figured out how to generate an assertion without actually using the `NSAssert` macros.
Note: The last code block has been corrected as the save was inadvertently being flipped.
I also use DLog() and ALog() macros, however my ALog() macro means “always log” rather than “assert log”. My macros look like this:
define ALog(format, …) NSLog((@”%s [L%d] ” format), PRETTY_FUNCTION, LINE, ##VA_ARGS);
ifdef DEBUG
define DLog(format, …) ALog(format, ##VA_ARGS);
else
define DLog(…)
endif
Is there any reason why you expand DLog() to “do { } while (0)” when DEBUG is undefined, rather than nothing?
This might work a little better:
http://gist.github.com/387747
Your ALog is functionally no different than just NSLog. The ALog I have defined above will stop code execution if it is hit. IMHO there is NEVER a reason to always log something. Logging slows down program execution and can cause fun synchronization bugs so should be turned off in production code in all except the most extreme situations (like the code is going to crash anyway).
As for the do {} while (0) instead of nothing is because there are a few rare code situations where replacing DLog(@””); with ; can cause issues. Replacing it with do {} while(0); is safer in those rare cases and will get optimized out by the compiler anyway.
Another suggestion for a Prefix.pch entry is the following:
static inline BOOL IsEmpty(id thing) {
return thing == nil
|| ([thing respondsToSelector:@selector(length)]
&& [(NSData *)thing length] == 0)
|| ([thing respondsToSelector:@selector(count)]
&& [(NSArray *)thing count] == 0);
}
This can come in handy is you want to check if a NSString, NSArray, NSSet or NSData variable is empty (variable is nil or does not contain any elements).
[…] the description at Cocoa is my Girlfriend. No […]
Your ZAssert macro and example implementation conflict, correct me if I’m wrong.
The macro’s conditional statement would read “if not condition”. The example implementation applies a “not [save success?]”.
As such the condition is built as follows, given a successful save: “if not (not true)”.
ZAssert raises an exception when the save is successful, which is the opposite of the desired outcome.
Easy fix: Remove the “!” from the condition defined in the macro or the “!” from the first argument passed to ZAssert.
Remaining consistent given it’s common to pass “if not” logic to NSAssert, I’ve opted to change the macro’s conditional to “if (condition)”. The implementation remains consistent with your example as well as existing NSAssert calls.
Thoughts?
The logic is correct. ZAssert should pass on a true which it does. Therefore a [moc save:&error] that returns YES will pass through as it should and one that failes with a NO will produce an error.
Cool, Marcus, this is better than what I was using. What’s the license on this?
The license is BSD as usually :). Feel free to use it as you want.
[…] Cocoa is My Girlfriend (also includes assert-related tricks) […]
[…] Cocoa Is My Girlfriend – My current Prefix.pch file […]
Markus, I shaynesweeney is right in so far as your example in the article is:
NSError *error = nil;
ZAssert(![managedObjectContext save:&error], @"My save failed: %@\n%@", [error localizedDescription], [error userInfo]);
So if save: returns a Yes, you are inverting it before passing it to ZAssert which then promptly re-inverts it.
define ZAssert(condition, ...) do { if (!(condition)) { ALog(VA_ARGS); }} while(0)
So a successful save goes from YES->NO->YES and an ALog is produced. I know first hand as it just happened to me :)
This works as expected
// if (![self.managedObjectContext save:&error]) {
// NSLog(@"My save failed: %@\n%@", [error localizedDescription], [error userInfo]);
// abort();
// }
ZAssert([self.managedObjectContext save:&error], @"My save failed: %@\n%@", [error localizedDescription], [error userInfo]);
Okay lesson learnt, don’t put code in comments :)
The example in this article is backwards. The line should read (as it does in my code samples n github):
[…] Cocoa Is My Girlfriend ยป My current Prefix.pch file Suggested DLog and ALog macros for iOS/XCode development (tags: cocoa xcode objective-c iphone development programming debugging) […]
[…] Prefix.pch macros to replace NSLog and NSAssert These macros are convenient drop-in replacements that allow you to have more control over logging and assertions. […]
[…] Some prerequisites – whatever sqlite addon you use for your projects, the method expects an NSDictionary with keys (sqlite column names) and the corresponding values. – I use ZAssert from Marcus S. Zarra (www.cimgf.com) […]