One problem with swizzling -[NSError dealloc]
to log errors is that you still have to pass a pointer to an NSError, otherwise there's no guarantee that the error will ever be created. For instance, it seems plausible that various framework methods might be implemented like this:
if (outError)
{
*outError = [[[NSError alloc] init] autorelease]; // or whatever.
}
You could make a global pointer, say:
NSError* gErrorIDontCareAbout = nil;
NSError** const ignoredErrorPtr = &gErrorIDontCareAbout;
...and declare it as extern
in your prefix header and then pass ignoredErrorPtr
to any method whose error you don't care to present, but then you lose any locality in terms of where the error occurred (and really this will only work right if you use ARC).
It occurs to me that what you really want to do is swizzle the designated initializer (or allocWithZone:
) and dealloc
and in that swizzled/wrapped method, call [NSThread callStackSymbols]
and attach the returned array (or perhaps its -description
) to the NSError instance using objc_setAssociatedObject
. Then in your swizzled -dealloc
you can log the error itself and the call stack where it originated.
But any way you do it, I don't think you can get anything useful if you just pass NULL
, because the frameworks are free to not create the NSError in the first place if you've told them you're uninterested in it (by passing NULL
).
You might do it like this:
@implementation MyAppDelegate
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Stash away the callstack
IMP originalIMP = class_getMethodImplementation([NSError class], @selector(initWithDomain:code:userInfo:));
IMP newIMP = imp_implementationWithBlock(^id(id self, NSString* domain, NSInteger code, NSDictionary* dict){
self = originalIMP(self, @selector(initWithDomain:code:userInfo:), domain, code, dict);
NSString* logString = [NSString stringWithFormat: @"%@ Call Stack: \n%@", self, [NSThread callStackSymbols]];
objc_setAssociatedObject(self, &onceToken, logString, OBJC_ASSOCIATION_RETAIN);
return self;
});
method_setImplementation(class_getInstanceMethod([NSError class], @selector(initWithDomain:code:userInfo:)), newIMP);
// Then on dealloc... (Note: this assumes that NSError implements -dealloc. To be safer you would want to double check that.)
SEL deallocSelector = NSSelectorFromString(@"dealloc"); // STFU ARC
IMP originalDealloc = class_getMethodImplementation([NSError class], deallocSelector);
IMP newDealloc = imp_implementationWithBlock(^void(id self){
NSString* logString = objc_getAssociatedObject(self, &onceToken);
if (logString.length) NSLog(@"Logged error: %@", logString);
originalDealloc(self, deallocSelector); // STFU ARC
});
method_setImplementation(class_getInstanceMethod([NSError class], deallocSelector), newDealloc);
});
}
@end
Note that this will log all errors, not just the ones you don't handle. That may or may not be acceptable, but I'm struggling to think of a way to make the distinction after the fact without making some call everywhere you do handle the error.