Adventures in the transition from C to Cocoa.

Wednesday, October 31, 2007

NSInvocation and version detection

After upgrading from Tiger to Leopard, We've had to deal with several interface changes (mostly because we're using undocumented API/SPI stuff). To make code that still works and compiles on both, we need to create dynamic messages to get past the compiler checks and to use different objects/methods depending on the OS version.

Solving version detection is trivial. There are a few places that document it, but they miss what I consider to be the simplest and most reliable way:

NSData *sysVerData = [[NSData alloc] initWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
NSDictionary *sysVer = [NSPropertyListSerialization propertyListFromData: sysVerData
mutabilityOption: NSPropertyListImmutable
format: NULL errorDescription: &errorString];
NSComparisonResult compare = [[sysVer objectForKey:@"ProductVersion"] compare:@"10.5"];

That's right. we get our information from the same place as sw_vers. If you think your app will be used on OS X Server, you'll want to change "SystemVersion.plist" to "ServerVersion.plist".

Then, on to NSInvocation.

NSInvocation is a way to invoke methods on objects dynamically. It's a bit tricky, and obviously a bit dangerous. However, with proper checking you can be perfectly safe.

Here's a trivial example of NSInvocation.


@interface anObject : NSObject
-(void) aMethod:(NSString*)arg;

@implementation anObject
-(void) aMethod:(NSString*)arg
NSLog(@"aMethod with %08x (%@) %@",arg, [arg className], arg);

int main()
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
NSString *arg = @"normal";

anObject *a = [[anObject alloc] init];

[a aMethod:nil];
[a aMethod:arg];

NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[anObject instanceMethodSignatureForSelector:@selector(aMethod:)]];
[inv setSelector:@selector(aMethod:)];
[inv setTarget: a];
NSLog(@"built invocation %08x",inv);

[inv setArgument:&arg atIndex:2];

NSLog(@"Set argument %08x",(void*)arg);

[inv invoke];

[p release];
return 0;

Important note that I ran into: when using [NSInvocation setArgument:..] objects need to be prefixed with an ampersand, "&". Without this, you'll get wonky results. If you're passing non-objects, you shouldn't use the ampersand.

There are a bunch of checking methods to see how many args a method supports and all that good stuff. You should really read the Documentation to get a good handle on what's going on.

No comments: