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.
#import
@interface anObject : NSObject
-(void) aMethod:(NSString*)arg;
@end
@implementation anObject
-(void) aMethod:(NSString*)arg
{
NSLog(@"aMethod with %08x (%@) %@",arg, [arg className], arg);
}
@end
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.