Adventures in the transition from C to Cocoa.

Tuesday, November 27, 2007

Too much, Too little

Objective-C 2.0 has completely changed the game (and rendered most of the earlier gdb tricks mentioned to no longer work as well). That said, it has also added some pretty slick new features that I hope I'll be able to give justice to once I get some more free time and some more coherent thoughts.

Just thought I'd let y'all know where things were.

Thursday, November 1, 2007

Best console log message ever

I was put between a rock and a hard place. Basically, I had to call a method on a class that isn't exported (i.e. I'm in plugin-space, and it needs to invoke something on an application-internal class that isn't exported). Through some trickery, I was able to get it working (man I love objective-C!). However, inserting the plugin into an application that doesn't have such a class results in an amazing console error that I've never seen before:


Nov 1 18:33:02 phendrana Photo Booth[33276]: *** NSInvocation: warning: object 0xfce0e0 of class 'specialInternalClass' does not implement methodSignatureForSelector: -- trouble ahead
Nov 1 18:33:02 phendrana Photo Booth[33276]: *** NSInvocation: warning: object 0xfce0e0 of class 'specialInternalClass' does not implement doesNotRecognizeSelector: -- abort


that "trouble ahead" part is awesome. I think it happens to any OC object that doesn't inherit from NSObject, but gets treated like it does. Fun stuff, that :)

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.


#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.

Categories