title | author | category | excerpt | status | ||
---|---|---|---|---|---|---|
NSAssertionHandler |
Mattt |
Cocoa |
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. |
|
"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 NSAssertionHandler
.
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.
Objective-C combines C-style assertion macros with an object-oriented approach to intercepting and handling assertion failures. Namely, NSAssertionHandler
:
Each thread has its own assertion handler, which is an object of class
NSAssertionHandler
. When invoked, an assertion handler prints an error message that includes the method and class names (or the function name). It then raises anNSInternalInconsistencyException
exception.
Foundation defines two pairs of assertion macros:
NSAssert
/NSCAssert
NSParameterAssert
/NSCParameterAssert
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 (NSParameterAssert
). As a rule of thumb, methods / functions should use NSParameterAssert
/ NSCParameterAssert
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
orNSParameterAssert
fails,-handleFailureInMethod:object:file:lineNumber:description:
is called in the assertion handler. - When a condition in
NSCAssert
orNSCParameterAssert
fails,-handleFailureInFunction:file:lineNumber:description:
is called in the assertion handler.
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.
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. NSAssertionHandler
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
NSAssertionHandler
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.
NSAssertionHandler
is a straightforward class, with two methods to implement in your subclass: -handleFailureInMethod:...
(called on a failed NSAssert
/ NSParameterAssert
) and -handleFailureInFunction:...
(called on a failed NSCAssert
/ NSCParameterAssert
).
LoggingAssertionHandler
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.
@interface LoggingAssertionHandler : NSAssertionHandler
@end
@implementation LoggingAssertionHandler
- (void)handleFailureInMethod:(SEL)selector
object:(id)object
file:(NSString *)fileName
lineNumber:(NSInteger)line
description:(NSString *)format, ...
{
NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%i", NSStringFromSelector(selector), object, fileName, line);
}
- (void)handleFailureInFunction:(NSString *)functionName
file:(NSString *)fileName
lineNumber:(NSInteger)line
description:(NSString *)format, ...
{
NSLog(@"NSCAssert Failure: Function (%@) in %@#%i", functionName, fileName, line);
}
@end
Each thread has the option of specifying an assertion handler. To have the NSAssertionHandler
subclass start handling failed assertions, set it as the value for the NSAssertionHandlerKey
key in the thread's threadDictionary
.
In most cases, it will make sense to set your assertion handler on the current thread inside -application: didFinishLaunchingWithOptions:
.
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init];
[[[NSThread currentThread] threadDictionary] setValue:assertionHandler
forKey:NSAssertionHandlerKey];
<#...#>
return YES;
}
NSAssertionHandler
reminds us of the best practices around articulating our expectations as programmers through assert statements.
But if we look deeper into NSAssertionHandler
—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.