Search code examples
objective-ccocoaitunesscripting-bridge

Scripting Bridge and filtering SBElementArrays using NSPredicate and FourCharCodes


I'm experimenting with Scripting Bridge for the the first time, but have run into an issue with filtering a SBElementArray according to a NSPredicate containing a FourCharCode enum constant as a criterion.

I wrote a trivial program to identify the "library" source in a user's iTunes library, by using -filteredArrayUsingPredicate: to filter the SBElementArray of all iTunes sources. I was expecting to get back an SBElementArray that, when evaluated, would produce an array of one element, namely the library source. Instead, when I call -get on the returned SBElementArray, I get back an empty array.

Perplexingly, if change the order and instead call -get on the SBElementArray of all sources to get a concrete NSArray, and call -filteredArrayUsingPredicate: on this array with the same predicate as before, I do get the desired result. I don't believe this is supposed to be necessary however, and I've had success filtering a SBElementArray using other NSPredicates (e.g. @"name=='Library'" works fine).

The code snippet is below. iTunesESrcLibrary is a FourCharCode constant defined in the header file generated by Scripting Bridge. (iTunesESrcLibrary = 'kLib'). I'm running 10.6.5.

iTunesApplication* iTunes = [[SBApplication alloc] initWithBundleIdentifier:@"com.apple.iTunes"];   

NSPredicate* libraryPredicate = [NSPredicate predicateWithFormat:@"kind == %u", iTunesESrcLibrary];

SBElementArray* allSources_Attempt1 = [iTunes sources];
SBElementArray* allLibrarySources_Attempt1 = (SBElementArray*)[allSources_Attempt1 filteredArrayUsingPredicate:libraryPredicate];

NSLog(@"Attempt 1: %@", allLibrarySources_Attempt1);
NSLog(@"Attempt 1 (evaluated): %@", [allLibrarySources_Attempt1 get]);


NSArray* allSources_Attempt2 = [[iTunes sources] get];
NSArray* allLibrarySources_Attempt2 = [allSources_Attempt2 filteredArrayUsingPredicate:libraryPredicate];

NSLog(@"Attempt 2: %@", allLibrarySources_Attempt2);

The output I get is the following:

Attempt 1: <SBElementArray @0x3091010: ITunesSource whose 'cmpd'{ 'relo':'=   ', 'obj1':'obj '{ 'want':'prop', 'from':'exmn'($$), 'form':'prop', 'seld':'pKnd' }, 'obj2':1800169826 } of application "iTunes" (88827)>
Attempt 1 (evaluated): (
)
Attempt 2: (
"<ITunesSource @0x3091f10: ITunesSource id 65 of application \"iTunes\" (88827)>"
)

Solution

  • I think I've figured it out. It seems you can't simply use a FourCharCode's integer value directly in a NSPredicate that you intend to use to filter an SBElementArray.

    By chance, I found that instead of:

    [NSPredicate predicateWithFormat:@"kind == %u", iTunesESrcLibrary]
    

    you need to use:

    [NSPredicate predicateWithFormat:@"kind == %@", [NSAppleEventDescriptor descriptorWithTypeCode: iTunesESrcLibrary]]
    

    Using this second form, I can filter the SBElementArray sources list as expected. However, this new predicate can't be used to filter the NSArray, even though this array is just the evaluated form of the SBElementArray! Here, you have to switch back to the %u version.

    Rant:
    Frankly this sucks, and it seems the sort of thing Scripting Bridge should deal with so I don't have to; I shouldn't have to know what an NSAppleEventDescriptor is. And while it's reasonable that not all predicates that work with NSArray should work with SBElementArray, the converse should not be the case and it's unnecessarily confusing that it is.