Search code examples
macosapplescript-objccocoa-scripting

Cocoa Scripting: Accept and return NSData


In order to support binary data exchange in my scriptable Mac app, I like to make it possible to receive and deliver data as NSData, using the AS-ObjC bridge, if that's possible.

For instance, I like to make this code possible in AppleScript:

use framework "Foundation"

set theData to current application's NSData's dataWithContentsOfFile:"/some/binary/file"

tell application "MyApp"
    set raw value to theData
end tell

The sdef contains a value-type and property for this:

<suite name="My Suite" code="Demo">
    <value-type name="ObjCNSData" code="NSDa">
        <cocoa class="NSData"/>
    </value-type>
    <class name="application" code="capp">
        <property name="raw data" code="rawD" type="ObjCNSData">
            <cocoa key="rawData"/>
        </property>

I then implement the conversion handler as an extension to NSData, similarly to how the Sketch example converts NSColor to the value-type "RGB Color":

@implementation NSData(DemoScripting)
+ (NSData *)scriptingObjCNSDataWithDescriptor:(NSAppleEventDescriptor *)desc {
    id res = [desc coerceToDescriptorType:'NSDa'];
    // -> res is NULL, which is not getting me any further
}

The desc's description is:

<NSAppleEventDescriptor: 'obj '{
  'form':'ID  ',
  'want':'ocid',
  'seld':'optr'($E0A8430080600000$),
  'from':null()
}>

Similarly, invoking [NSScriptObjectSpecifier _scriptingSpecifierWithDescriptor:descriptor] returns NULL as well.

So, how do I get to the actual NSData object inside my app code?

And how do I return a NSData object to the AppleScript?


Solution

  • Shane Stanley did indeed know a way, and it does not even require extra code in my app - instead, it can all be done in AppleScript, with these two conversion functions:

    use framework "Foundation"
    
    set nsData1 to current application's NSData's dataWithContentsOfFile:"/etc/hosts"
    set asData to my ASDataFromNSData(nsData1)
    set nsData2 to my NSDataFromASData(asData)
    
    on ASDataFromNSData(theData)
        set theCode to current application's NSHFSTypeCodeFromFileType("'rdat'")
        return (current application's NSAppleEventDescriptor's descriptorWithDescriptorType:theCode |data|:theData) as data
    end ASDataFromNSData
    
    on NSDataFromASData(asData)
        return (current application's NSArray's arrayWithObject:asData)'s firstObject()'s |data|()
    end NSDataFromASData
    

    It appears that rdat is a special AppleScript type for this purpose, with the framework automatically handling the conversion with NSData. I can't find that type declared in the AE.framework's headers, though.

    I then still have to handle this rdat type explicitly in my app's code, though. But I won't need the value-type in the sdef, and can change the property to:

    <property name="raw data" code="rawD" type="any">
        <cocoa key="rawData"/>
    </property>
    

    Returning data as rdat is similar. My -rawData method:

    return [NSAppleEventDescriptor descriptorWithDescriptorType:'rdat' data:myNSData];
    

    This only works if I declare the property type as "any", though. If I use type="rdat", Script Debugger shows the type as a dedicated raw data type, but then I get -10000 errors when trying to set or get the property in a script.