Vincent Gable’s Blog

December 12, 2008

One assert() You Need?

Accidently disconnected outlets in shipping Cocoa apps are legend.

Jonathan “Wolf” Rentzsch

From what I can tell assert() is slowly going the way of the goto in the programming world. Exceptions, unit-tests, and other modern software engineering practices, seem to have a better answer for testing something at runtime, and ensuring that you never enter a bad state.

But there is one case where I think you should put an assert() (and not an NSAssert()) in your code: in awakeFromNib assert() that every IBOutlet is connected.

It’s surprisingly easy to accidentally disconnect something in Interface Builder, or rename something in Xcode. I’ve done it before. More then once. assert()-ing IBOutlets has saved me a lot of debugging time.

Hard to Find In Code

An IBOutlet that is not connected is nil. Because Objective-C quietly ignores messages sent to nil, it’s very easy not to notice the problem for a while; then spend a long time debugging a side effect of the issue.

Hard to Find By Eye

A widget in a nib/xib file that is not connected will never change it’s state. But it typically it has a reasonable initial state. This makes it difficult to detect disconnected IBOutlets by eye, because things will look right until they are supposed have changed, but the eye is drawn to change, and de-emphasizes unchanging things.

Why assert()?

As Wolf says, the worst-case-scenario is that right before release you make a trivial change in Interface Builder (“We can’t ship with that window saying “Claculator”), an IBOutlet gets disconnected, and nobody notices in time.

But an assert() failing will be noticed with casual testing, and triggered if the nib is loaded at all, even if the series of interactions needed to cause the widget to change state are not performed.

An assert() is very light-weight and easy to do. It’s exactly one line of code, and you don’t have to add a unit testing framework to your project to do it. And that makes it perfect for test projects. I’m a believer in getting a feature working in a test project first, then copying it into your real product. (Honestly I don’t do it as much as I should, and I almost always kick myself for it.) It lets you test and learn without hacking-up your product. Plus, new things are often easier to try without the weight of a big code base.

Speaking of unit tests, I don’t have complete faith in them here. That’s because a disconnected IBOutlet in a shipping program is fundamentally an issue with the way the release-build is configured. So anything that’s not testing the the actual release build is not exhaustively testing for this problem. an OCUnit-test can catch a disconnected IBOutlet if it is loaded into the application. But in my experience, such tests are cumbersome. Running them involves launching your full application, and programatically manipulating it. This can take a while to run since all the UI will be displayed and animated. And you have to be careful about state since each test will be changing the state of your program.

Ultimately, assert() just seems to be optimal, in dependability and simplicity, for catching a disconnected IBOutlet.

But I suspect there’s a better way I don’t know.

How do you catch IBOutlets?

December 4, 2008

NSAssert Considered Harmful

Filed under: Bug Bite,Cocoa,MacOSX,Objective-C,Programming,Tips | , , , ,
― Vincent Gable on December 4, 2008

The NSAssert() function is unreliable, and therefore dangerous. Plain-old assert() is a better choice.

Note that #define NDEBUG disables assert(); and #define NS_BLOCK_ASSERTIONS disables NSAssert(). One kind of assert being disabled does not mean the other kind is. Nether kind of assertion is disabled by default when building in “Release” mode — you must be sure to disable them yourself. Be careful, a user will not appreciate the difference between a bug and a failed assertion — both are a crash.

assert() immediately stops your program with a SIGABORT signal. You can’t miss a failed assert(), its behavior is predictable, and it fails fast (link is a PDF).

But a failed NSAssert() behaves unpredictably. Sometimes it stops program execution, sometimes it does not and leaves the program in a strange inconsistent state. I have entirely missed NSAssert()s failing, because I did not look at the console. if(!expr) NSLog(); would have been a better choice in the those cases, because at least I would have known how it behaved.

According to Apple,

Assertion macros, such as NSAssert and NSCAssert … pass a string to an NSAssertionHandler object describing the failure. Each thread has its own NSAssertionHandler object. When invoked, an assertion handler prints an error message that includes the method and class (or function) containing the assertion and raises an NSInternalInconsistencyException.

Complicated! The complexity means it is possible to customize what happens when an NSAssert() fails. That sounds cool, but I’ve never heard of someone needing to actually do that.

If a framework catches NSInternalInconsistencyExceptions, then your program will keep right on running after a failed NSAssert(). I have had this happen to me several times. I apologize for not having taken the time to investigate what frameworks were catching what.

Apple could change what catches what with any software update.

Variability and complexity are the last things you want while debugging. There’s no reason to invite it them by using NSAssert() over assert(). Since NSAssert() is not guaranteed to stop your program, it can not be relied on to guard against data corruption, or anything worse then a predictable crash.

UPDATE 2009-06-01: You can annotate assert(), so it prints an explanation like NSAssert(), by &&ing in a string after the condition. For example assert(i < j) is a lot more useful with an explanation: assert(i < j && "unexpected cycle in graph") — on failure it prints

Assertion failed: (i < j && “unexpected cycle in graph”), function WillHalt(), file /path/to/code.m, line 30.

Powered by WordPress