1
votes

I have a very basic Cocoa application written in Xcode, and I'm trying to access a property of my application delegate with a simple AppleScript script:

tell application "HelloWorld"
    set appDelegateProperty to property1
end tell

The information in the Cocoa Scripting Guide seems pretty straight forward. While it seems like the code I've written is key-value coding (KVC) compliant, I am seeing errors that indicate otherwise.

I've added a synthesized property of type NSString named property1 to my AppDelegate class, setting it to @"test" in the applicationDidFinishLaunching() method. And I added entries to my scripting definition (SDEF) file to allow access to the property from AppleScript.

Here's the relevant code (you can download a zipped copy, or browse the source, of the Xcode project on this web page):

Info.plist

I added these keys to enable AppleScript support in the application:

<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>OSAScriptingDefinition</key>
<string>HelloWorld.sdef</string>

AppDelegate.h

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>
@property   NSString*   property1;
@end

AppDelegate.m

#import "AppDelegate.h"

@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end

@implementation AppDelegate
@synthesize property1;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    property1 = @"test";
}
@end

HelloWorld.sdef

<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
 <dictionary title="HelloWorld">
    <suite name="HelloWorld" code="HELO" description="HelloWorld scripting suite">
        <class name="application" id="HELO" code="capp" description="top-level scripting object">
            <cocoa class="NSApplication"/>
            <property name="property1" code="Hadp" description="property1" type="text" access="r">
                <cocoa key="property1"/>
            </property>
        </class>
    </suite>
 </dictionary>

When I start the application in the Xcode debugger and execute this AppleScript code in Script Editor, here is the error I see in the Script Editor console:

Script Editor Console Output

 error "HelloWorld got an error: AppleEvent handler failed." number -10000

Xcode Debugger Console Output

 2016-07-01 15:29:18.449 HelloWorld[90280:3166995] Command: Intrinsics.get
Direct Parameter: <NSPropertySpecifier: property1>
Receivers: <NSPropertySpecifier: property1>
Arguments:     {
}
 2016-07-01 15:29:18.449 HelloWorld[90280:3166995] An exception was thrown during execution of an NSScriptCommand...
 2016-07-01 15:29:18.449 HelloWorld[90280:3166995] [<NSApplication 0x620000101560> valueForUndefinedKey:]: this class is not key value coding-compliant for the key property1.
 2016-07-01 15:29:18.449 HelloWorld[90280:3166995] Result: (null)
 2016-07-01 15:29:18.449 HelloWorld[90280:3166995] Error: -10000 "(null)"

Troubleshooting Notes

Somehow, my code is not in fact KVC compliant, though I am unsure how or why.

I suspect there may be an issue with the fact that the property is in the application delegate rather than the NSApplication class itself, but I could be wrong.

I've set the bindings debug log level as shown in the "Troubleshooting Cocoa Bindings" section of the Cocoa Bindings Programming Topics documentation to troubleshoot the bindings:

defaults write net.none.HelloWorld NSBindingDebugLogLevel 1

I've also set the scripting debug log level to 1 as shown in the "Turn On Debugging Output for Scripting" section of the Cocoa Scripting Guide to see more detailed debug output like so:

defaults write net.none.HelloWorld NSScriptingDebugLogLevel 1

Finally, I've followed instructions in the "Examining Scriptability Information in Your Application" section of the Cocoa Scripting Guide to output the descriptions of scripting information Cocoa pulls from the SDEF. Here's that debugger console command and its output:

po [NSClassFromString(@"NSScriptSuiteRegistry") sharedScriptSuiteRegistry]

Suite: Intrinsics ('intr'), hidden: yes
    Name: "Intrinsics", description: ""
    Class: item ('cobj'), superclass: <none>, hidden: no
        Implementation class: NSObject
        Name: "item", plural name: "items", description: "A scriptable object."
        Attribute: classCode ('pcls'), type: type ('type'), access: read-only, hidden: no
            Name: "class", description: "The class of the object."
        Attribute: scriptingProperties ('pALL'), type: record ('reco'), access: read-write, hidden: no
            Name: "properties", description: "All of the object's properties."
        Default subcontainer: <none>
        Supported command: delete, method: -
        Supported command: exists, method: -
        Supported command: set, method: -
        Supported command: get, method: -
        Supported command: count, method: -
        Supported command: move, method: -
        Supported command: duplicate, method: -
        Primitive type: <none>
    Command: get ('core'/'getd'), hidden: no
        Implementation class: NSGetCommand
        Name: "get", description: "Returns the value of the specified object(s)."
        Unnamed argument ('----'), type: specifier ('obj '), optional: no
            (No user-readable name or description needed for unnamed arguments)
        Result type: any ('****')
            Description: <none>
    Command: set ('core'/'setd'), hidden: no
        Implementation class: NSSetCommand
        Name: "set", description: "Sets the value of the specified object(s)."
        Unnamed argument ('----'), type: specifier ('obj '), optional: no
            (No user-readable name or description needed for unnamed arguments)
        Argument: Value ('data'), type: any ('****'), optional: no, hidden: no
            Name: "to", description: "The new value."
        Result type: <none> ('null')
            Description: <none>
    Value type: any ('****')
        Implementation class: NSAppleEventDescriptor
    Value type: boolean ('bool')
        Implementation class: NSNumber
    Value type: date ('ldt ')
        Implementation class: NSDate
    Value type: file ('file')
        Implementation class: NSURL
    Value type: integer ('long')
        Implementation class: NSNumber
    Value type: location specifier ('insl')
        Implementation class: NSPositionalSpecifier
    Value type: missing value ('msng')
        Implementation class: NSNull
    Value type: number ('nmbr')
        Implementation class: NSNumber
    Value type: point ('QDpt')
        Implementation class: NSData
    Value type: real ('doub')
        Implementation class: NSNumber
    Value type: record ('reco')
        Implementation class: NSDictionary
    Value type: rectangle ('qdrt')
        Implementation class: NSData
    Value type: specifier ('obj ')
        Implementation class: NSScriptObjectSpecifier
    Value type: text ('ctxt')
        Implementation class: NSString
    Value type: type ('type')
        Implementation class: NSNumber
    Object type: item ('cobj')

 Suite: HelloWorld ('HELO'), hidden: no
    Name: "HelloWorld", description: "HelloWorld scripting suite"
    Class: application ('capp'), superclass: item, hidden: no
        Implementation class: NSApplication
        Name: "application", plural name: "applications", description: "top-level scripting object"
        Attribute: property1 ('Hadp'), type: text ('ctxt'), access: read-only, hidden: no
            Name: "property1", description: "property1"
        Default subcontainer: <none>
        Primitive type: <none>
    Object type: application ('capp')

Any ideas?

1
Note: I notice a somewhat related question here, but it does not use synthesized properties and seems to be a slightly different problem than mine.Jolly Roger

1 Answers

1
votes

If the key is in AppDelegate you have to implement

- (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key
{
    return [key isEqualToString:@"property1"]; 
}

For multiple keys declare a NSSet property keySet containing all keys and return

return [keySet containsObject:key]; 

You don't need to specify the cocoa key property1 in the sdef file since the name match the selector.