Question

I am just learning how to use ScriptingBridges. I made a method that slowly fades the volume on iTunes, and would like to make it a category so I can do the following:

iTunesApplication* iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
[iTunes lowerVolume:50 speed:1];

I made another category for NSSpeechSynthesizer that works, but I can't get this one to. I keep getting the following build error:

"_OBJC_CLASS_$_iTunesApplication", referenced from:
l_OBJC_$_CATEGORY_iTunesApplication_$_iTunesApplicationAdditions in iTunesApplication.o
objc-class-ref-to-iTunesApplication in iTunesApplication.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

Is there something special I can do to make it work since I can't include the symbols?

Thanks,
Ryan Pendleton

UPDATE: I only found one solution, which is below. It involves MethodSwizzling, so I'm open to better answers, but for now it's all I have.

Was it helpful?

Solution

The solution I found was to use the Objective-C runtime API. I'm sure there's a better way to organize this, but here's how I did it:

Here are my .h and .m files for creating the category. Notice how lowerVolume is not an actual method, but a C function with the arguments id self, and SEL _CMD. You'll also notice a setupCategories function. We'll call that later.

// iTunes+Volume.h

#import <objc/runtime.h>
#import "iTunes.h"

void lowerVolume(id self, SEL _cmd, int dest, float speed);
void setupCategories();

@interface iTunesApplication (Volume)

- (void)lowerVolume:(int)dest speed:(float)speed;

@end

// iTunes+Volume.m

#import "iTunes+Volume.h"

void lowerVolume(id self, SEL _cmd, int dest, float speed)
{
    NSLog(@"Lower Volume: %i, %f", dest, speed);
}

void setupCategories()
{
    id object = [[SBApplication alloc] initWithBundleIdentifier:@"com.apple.iTunes"];
    Class class = [object class];
    [object release];

    class_addMethod(class, @selector(lowerVolume:speed:), (IMP)lowerVolume, "@:if");
}

Now that I've made the functions, I need to actually add them to the scripting bridge class using the Objective-C runtime API. I'll do this in main.m to make sure that the methods are ready to be used when the run loop starts.

// main.m

#import <Cocoa/Cocoa.h>
#import "iTunes+Volume.h"

int main(int argc, char *argv[])
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    setupCategories();
    return NSApplicationMain(argc,  (const char **) argv);

    [pool drain];
}

Now, I can use my method wherever I want as long as I include the header files:

- (void)mute
{
    iTunesApplication* iTunes = [[SBApplication alloc] initWithBundleIdentifier:@"com.apple.iTunes"];
    [iTunes lowerVolume:0 speed:1];
    [iTunes release];
}

If any of this doesn't make sense, just tell me and I'll try to explain it better.

OTHER TIPS

I think you need to include -framework ScriptingBridge to your gcc arguments. That got it to compile for me!

As noted above, you can't easily do a category on iTunesApplication because it doesn't exist at compile time, and also because the runtime class name is ITunesApplication (capital "I").

The best solution I've found is to do your category on the class that DOES exist, SBApplication. Here's the code I tested that works and does what the original example was trying to do:

//  SBApplication+Extensions.h

@import ScriptingBridge;

@interface SBApplication (Extensions)

- (void)lowerVolume:(int)dest speed:(float)speed;

@end

//  SBApplication+Extensions.m

#import "iTunes.h"
#import "SBApplication+Extensions.h"

@implementation SBApplication (Extensions)

- (void)lowerVolume:(int)dest speed:(float)speed
{
    NSLog(@"Lower Volume: %i, %f", dest, speed);
}

@end

// Caller, say in AppDelegate

#import "SBApplication+Extensions.h"

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{

    iTunesApplication *iTunesApp =
        [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];

    [iTunesApp lowerVolume:4 speed:3.3f];
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top