Uli's Web Site |
|
blog |
UKHelperMacros
One of the bits of code I use a lot is a collection of a few small macros, which I can #import from my precompiled header. It contains a few neat ones: UKLogEven with Xcode 3's mini debugger that lets you debug an application without bringing it to the front, and with the ability to use dtrace (aka Instruments and X-Ray) to set breakpoints that will fire the third time in only, or display certain variables, you'll often find yourself needing to do what people used to call printf-debugging. I.e. you'll be scattering NSLog statements throughout your code, which print some useful bit of information. Now, users generally don't appreciate applications that have a case of Logorrhoea and keep spilling messages onto their hard disk. So, you have to remember to remove all those NSLogs later. Or, you write a macro. UKLog is one like this. What I do first is to go to the Project Settings window in Xcode, and there to the build settings for the "Development" build configuration. There, I add DEBUG=1 to the "Preprocessor Macros" entry. Now, I can use #if to detect whether my code is being built for development or deployment, and map UKLog to NSLog for development builds, and to nothing for release builds: #if DEBUG #define UKLog(args...) NSLog(args) #else #define UKLog(args...) // do nothing. This is a variadic macro, that is, I can pass an arbitrary number of parameters to it, and it'll always pass those parameters to NSLog. But while we're doing this, we may as well make the message better. It's always a bit annoying to have to locate the method that printed a particular bit of text, so why not include that: #define UKLog(args...) NSLog( @"%s: %@", __PRETTY_FUNCTION__, [NSString stringWithFormat: args])__PRETTY_FUNCTION__ is a preprocessor constant defined by GCC that contains a C string with the name of the current function. So, this will log something like: -[MyObject release]: User didn't call -defrobnitz before releasing. ASSIGN and DESTROYOne of the more annoying aspects of Cocoa programming is writing accessors and destructors. Especially if you're paranoid enough to insist on clearing released instance variables to NIL. So, I wrote myself two macros to help me with this: #define DESTROY(obj) do {\ [obj release];\ obj = nil;\ } while(0) Ignore the do { } while(0) part. That's just so this macro's several statements don't confuse if, then and other control structures, and behaves just like a function call. This way, your destructors turn from: -(void) dealloc { [foo release]; foo = nil; [bar release]; bar = nil; [super dealloc]; }into: -(void) dealloc { DESTROY(foo); DESTROY(bar); [super dealloc]; } Moreover, if you're writing a library that should be usable both using taditional reference counting and using the garbage collector, you can change this to simply set variables to NIL when built for GC, and thus the same code will behave correctly for both cases. Of course, that's only useful if you use DESTROY elsewehere than your destructor, because destructors don't get called under GC. The second macro is ASSIGN: #define ASSIGN(targ,newval) do {\ id __UKHELPERMACRO_OLDTARG = (id)(targ);\ (targ) = [(newval) retain];\ [__UKHELPERMACRO_OLDTARG release];\ } while(0) This simply takes care of releasing the previous object pointed to by an instance variable and retaining the new one. Your mutators will do the following transformation: -(void) setFoo: (NSString*)inFoo { NSString* old = foo; foo = [inFoo retain]; [old release]; } -(void) setFoo: (NSString*)inFoo { ASSIGN(foo,inFoo); } Less typing, and more importantly, less opportunity for mistyping. However, note that this macro expects the first argument, targ, to be an instance variable. If it is an expression, this expression will be evaluated twice, so don't pass *(++myArray) to it, or you'll increment two times, and have a stale pointer in the first and smash the pointer in the second. CREATE_AUTORELEASE_POOLWhen you start a new thread, or have code in your main() function, you generally have to create an autorelease pool so you don't leak objects: NSAutoreleasePool* aPool = [[NSAutoreleasePool alloc] init]; // Code goes here... [aPool release]; aPool = nil;With our macros, you have to type a bunch less (for one, you only have to type "NSAutoReleasePool" once), and again it's less likely you'll make a typo: CREATE_AUTORELEASE_POOL(aPool); // Code goes here... DESTROY(aPool); These macros don't just let you avoid typos, at the same time they make your code more readable, and the pool creation/teardown and object creation/teardown calls will stand out more. And it'll enforce good coding practices, as all released object pointers get zeroed and you can't get weird behaviour from using stale pointers anymore. And finally, you could do your own zombie-detection by using some object instead of NIL in debug builds, kind of like NSZombie works. However, these objects, *you* control, so you can easily make them behave like a NIL object, except when a particular method is called where you know someone is messaging a released object. You can get these UKHelperMacros on my Source Code page. The UKHelperMacros are free to use, change and redistribute, as long as you don't distribute modified versions and claim I had done the modifications, so feel free to use them wherever you feel like it. By the way: GNUstep defines its own ASSIGN, DESTROY and CREATE_AUTORELEASE_POOL macros, so I decided to use the same names and parameters to encourage creation of a standard for this.
|
Created: 2007-11-01 @054 Last change: 2024-12-26 @222 | Home | Admin | Edit © Copyright 2003-2024 by M. Uli Kusterer, all rights reserved. |