We’ve all got little libraries of code or scripts that help us with debugging. Often these are for logging information in a particular way, or wrapping logs/tests such that they’re only invoked in Debug builds but not in production. Or they clean up your IDE’s brainfarts.
Having created these debug libraries, how are you going to get your production code to use them? Are you really going to sprinkle MyCompanyDebugLog(fmt,…) messages throughout your app?
Introducing step one on the road to sanity: the debug proxy. This is useful when you want to find out how a particular class gets used, e.g. when it provides callbacks that will be invoked by a framework. You can intercept all the messages to the object, and inspect them as you see fit. Here’s the code (written with the assumption that ARC is enabled):
FZADebugProxy.h
#import <Foundation/Foundation.h>
@interface FZADebugProxy : NSProxy
- (id)initWithTarget: (NSObject *)aTarget;
@end
FZADebugProxy.m
#import "FZADebugProxy.h"
@implementation FZADebugProxy {
NSObject *target;
}
- (id)initWithTarget:(NSObject *)aTarget {
target = aTarget;
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *signature = [target methodSignatureForSelector: sel];
if (signature == nil) {
signature = [super methodSignatureForSelector: sel];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [target respondsToSelector: aSelector] ? YES : [super respondsToSelector: aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
invocation.target = target;
SEL aSelector = [invocation selector];
(void)aSelector;
[invocation invoke];
}
@end
And no, there isn’t a bug in the -initWithTarget: method. The slightly clumsy extraction of the selector in -forwardInvocation: is done to avoid a common problem with using Objective-C inside the debugger where it decides it doesn’t know the return type of objc_msgSend() and refuses to call the method.
You would use it like this. Here, I’ve modified the app delegate from BrowseOverflow to use a proxy object for the object configuration – a sort of domain-specific IoC container.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BrowseOverflowViewController *firstViewController = [[BrowseOverflowViewController alloc] initWithNibName: nil bundle: nil];
firstViewController.objectConfiguration = (BrowseOverflowObjectConfiguration *)[[FZADebugProxy alloc] initWithTarget: [[BrowseOverflowObjectConfiguration alloc] init]];
TopicTableDataSource *dataSource = [[TopicTableDataSource alloc] init];
[dataSource setTopics: [self topics]];
firstViewController.dataSource = dataSource;
self.navigationController.viewControllers = [NSArray arrayWithObject: firstViewController];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
The bold line is the important change. The cast silences the compiler’s strict type-checking when it comes to property assignment, because it doesn’t believe that NSProxy is of the correct type. Remember this is only debug code that you’re not going to commit: you could switch to a plain old setter, suppress the warning using diagnostic pragmas or do whatever you want here.
At this point, it’s worth running the unit tests and using the app to convince yourself that the behaviour hasn’t changed at all.
So, how do you use it? Shouldn’t there be an NSLog() or something in the proxy class so you can see when the target’s messaged?
No.
Step two on the road to sanity is to avoid printf()-based debugging in all of its forms. What you want to do here is to use Xcode’s debugger actions so that you don’t hard-code your debugging inspection capabilities into your source code.
Set a breakpoint in -[FZADebugProxy forwardInvocation:]. This breakpoint will be met whenever the target object is messaged. Now right-click on the breakpoint marker in the Xcode source editor’s gutter and choose “Edit Breakpoint…” to bring up this popover.

In this case, I’ve set the breakpoint to log the selector that was invoked, and crucially to continue after evaluation so that my app doesn’t stop in the debugger every time the target object is messaged. After a bit of a play with the app in the simulator, the debug log looks like this:
GNU gdb 6.3.50-20050815 (Apple version gdb-1752) (Sat Jan 28 03:02:46 UTC 2012) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "x86_64-apple-darwin".sharedlibrary apply-load-rules all Attaching to process 3898. Pending breakpoint 1 - ""FZADebugProxy.m":37" resolved Current language: auto; currently objective-c 0x10271: "stackOverflowManager" 0x102a0: "avatarStore" 0x10271: "stackOverflowManager" 0x10271: "stackOverflowManager" 0x102a0: "avatarStore"
Pretty nifty, yes? You can do a lot with Xcode’s breakpoint actions: running a shell script or an AppleScript are interesting options (you could have Xcode send you an iMessage every time it send your target an Objective-C message). Speaking out the names of selectors is fun for a very short while, but not overly useful.
Xcode’s breakpoint actions give you a much more powerful debugging capability than NSLog(). By using breakpoint actions on an Objective-C proxy object, you can create highly customisable aspect-oriented techniques for debugging your code.





