NSAssertionHandler
“When at first you don’t succeed, use an object-oriented injection point to override default exception handling.” This is the sort of advice you would have learned at mother’s knee if you were raised by NSAssertion
.
Programming incorporates numerous disciplines of human reasoning, from high-level discourse and semantics—the “story” we tell each other to explain how a system works—to the mathematical and philosophical machinery that underpins everything.
Assertions are a concept borrowed from classical logic. In logic, assertions are statements about propositions within a proof. In programming, assertions denote assumptions the programmer has made about the application at the place where they are declared.
When used in the capacity of preconditions and postconditions, which describe expectations about the state of the code at the beginning and end of execution of a method or function, assertions form a contract. Assertions can also be used to enforce conditions at run-time, in order to prevent execution when certain preconditions fail.
Assertions are similar to unit testing in that they define expectations about the way code will execute. Unlike unit tests, assertions exist inside the program itself, and are thereby constrained to the context of the program. Because unit tests are fully independent, they have a much greater capacity to isolate and test certain behaviors, using tools like methods stubs and mock objects. Developers should use assertions and unit tests in combination and in reasonable quantity to test and define behavior in an application.
Foundation Assertion Handling
Objective-C combines C-style assertion macros with an object-oriented approach to intercepting and handling assertion failures. Namely, NSAssertion
:
Each thread has its own assertion handler, which is an object of class
NSAssertion
. When invoked, an assertion handler prints an error message that includes the method and class names (or the function name). It then raises anHandler NSInternal
exception.Inconsistency Exception
Foundation defines two pairs of assertion macros:
-
NSAssert
/NSCAssert
-
NSParameter
/Assert NSCParameter
Assert
Foundation makes two distinctions in their assertion handler APIs that are both semantic and functional.
The first distinction is between a general assertion (NSAssert
) and a parameter assertion (NSParameter
). As a rule of thumb, methods / functions should use NSParameter
/ NSCParameter
statements at the top of methods to enforce any preconditions about the input values; in all other cases, use NSAssert
/ NSCAssert
.
The second is the difference between C and Objective-C assertions: NSAssert
should only be used in an Objective-C context (i.e. method implementations), whereas NSCAssert
should only be used in a C context (i.e. functions).
- When a condition in
NSAssert
orNSParameter
fails,Assert -handle
is called in the assertion handler.Failure In Method:object:file:line Number:description: - When a condition in
NSCAssert
orNSCParameter
fails,Assert -handle
is called in the assertion handler.Failure In Function:file:line Number:description:
Additionally, there are variations of NSAssert
/ NSCAssert
, from NSAssert1
… NSAssert5
, which take their respective number of arguments to use in a printf
-style format string.
Using NSAssertionHandler
It’s important to note that as of Xcode 4.2, assertions are turned off by default for release builds, which is accomplished by defining the NS_BLOCK_ASSERTIONS
macro. That is to say, when compiled for release, any calls to NSAssert
& co. are effectively removed.
And while Foundation assertion macros are extremely useful in their own right—even when just used in development—the fun doesn’t have to stop there. NSAssertion
provides a way to gracefully handle assertion failures in a way that preserves valuable real-world usage information.
That said, many seasoned Objective-C developers caution against actually using
NSAssertion
in production applications. Foundation assertion handlers are something to understand and appreciate from a safe distance. Proceed with caution if you decide to use this in a shipping application.Handler
NSAssertion
is a straightforward class, with two methods to implement in your subclass: -handle
(called on a failed NSAssert
/ NSParameter
) and -handle
(called on a failed NSCAssert
/ NSCParameter
).
Logging
simply logs out the assertion failures, but those failures could also be logged to an external web service to be aggregated and analyzed, for example.
LoggingAssertionHandler.h
@interface Logging Assertion Handler : NSAssertion Handler
@end
LoggingAssertionHandler.m
@implementation Logging Assertion Handler
- (void)handle Failure In Method:(SEL)selector
object:(id)object
file:(NSString *)file Name
line Number:(NSInteger)line
description:(NSString *)format, ...
{
NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%i", NSString From Selector(selector), object, file Name, line);
}
- (void)handle Failure In Function:(NSString *)function Name
file:(NSString *)file Name
line Number:(NSInteger)line
description:(NSString *)format, ...
{
NSLog(@"NSCAssert Failure: Function (%@) in %@#%i", function Name, file Name, line);
}
@end
Each thread has the option of specifying an assertion handler. To have the NSAssertion
subclass start handling failed assertions, set it as the value for the NSAssertion
key in the thread’s thread
.
In most cases, it will make sense to set your assertion handler on the current thread inside -application:
did
.
AppDelegate.m
- (BOOL)application:(UIApplication *)application
did Finish Launching With Options:(NSDictionary *)launch Options
{
NSAssertion Handler *assertion Handler = [[Logging Assertion Handler alloc] init];
[[[NSThread current Thread] thread Dictionary] set Value:assertion Handler
for Key:NSAssertion Handler Key];
…
return YES;
}
NSAssertion
reminds us of the best practices around articulating our expectations as programmers through assert statements.
But if we look deeper into NSAssertion
—and indeed, into our own hearts, there are lessons to be learned about our capacity for kindness and compassion; about our ability to forgive others, and to recover from our own missteps. We can’t be right all of the time. We all make mistakes. By accepting limitations in ourselves and others, only then are we able to grow as individuals.
Or whatever.