I thought I should post some addition information on Cocoa Scriptability support for Core-data applications since there is so little information out there. I spent a least a month trying to understand how to come to terms with this mechanism.
Although I was able to provide AppleScript support for my core-data application using index specifiers, I found that employing 'uniqueID' specifiers provided a better fit. Better, because core data to-many relationships are supported by unordered sets, rather then arrays. Core data provides a way for you to specify a managed object by an object ID that can be evaluated by a Cocoa Scriptability method. Nevertheless, to implement support for a "too-many" relation using uniqueID specifiers, additional code elements are needed.
1) Provide a property element in the 'sdef' for each entity you will be supporting. For example, to support my 'level' entity I post the following within the 'levels' class tags:
<property name="id" code="ID " type="text" access="r" description="The level's unique id. This may be a temporary id for newly- created level objects, until they are saved.">
<cocoa key="uniqueID"/>
</property>
Note that the type is designated 'text' and not 'specifier'. The 'uniqueID' interchange between the AppleEvent mechanism and Cocoa AppleScript support will be a string value. Also notice that the 'cocoa key' value is 'uniqueID'. This key (typical of such keys in the 'sdef') is used by the AppleScript scriptability support to identify a method name in your application that conforms to a KVC pattern.
2) Post a 'valueInWithUniqueID' method within the class that contains the target objects. It is within this method that you provide a way to extract the managed object corresponding to whatever 'uniqueID' is passed to the method. Here's mine for my 'levelsArray' KVO method posted within my container class, 'Levels'.
- (id)valueInLevelsArrayWithUniqueID:(NSString *)uniqueID;
{
NSManagedObject *managedObject= [[[NSApp delegate] myManagedObjectContext] objectWithID:[[[NSApp delegate] lessonsManager] managedObjectIDForURIRepresentation:[NSURL URLWithString:uniqueID]]];
return managedObject;
}
And here is my 'sdef' property declaration for the contained 'unit' class:
<property name="id" code="ID " type="text" access="r" description="The unit's unique id. This may be a temporary id for newly-created unit objects, until they are saved.">
<cocoa key="uniqueID"/>
</property>
My 'Units' class also evokes the value method. Note the essential KVC name pattern:
- (id)valueInUnitsArrayWithUniqueID:(NSString *)uniqueID;
{
NSManagedObject *managedObject= [[[NSApp delegate] lessonsDBase] objectWithID:[[[NSApp delegate] lessonsManager] managedObjectIDForURIRepresentation:[NSURL URLWithString:uniqueID]]];
return managedObject;
}
With these in place, if my AppleScript needs to specify a 'level' object from its 'uniqueID', it will be answered. This seems to come into play when you have a hierarchy of entity classes and you write an AppleScript that 1) evokes a command that returns a reference to its result, and 2) acts upon this returned result with another command:
count (make new section at unit 1 of level 1)
Curiously, the following does not evoke the value method:
count (unit 1 of level 1)
Note that it lacks condition 1--a primary command (e.g. 'make') is missing. In this case, the implied AppleScript 'get' command is evoked, however Cocoa scriptability seems to determine the values of the entity hierarchy by another means.
3) Provide 'uniqueID' object specifiers for all objects returned from commands that you support, as well as explicitly named 'objectSpecifier' for each of entity sub-classes that support their implied 'get' commands. For example, my 'clone' command, posted within its 'performDefaultImplementation' provides the following method:
NSUniqueIDSpecifier *uniqueIDSpecifier = [[[NSUniqueIDSpecifier allocWithZone:[self zone]] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:sourceContainerSpecifier key:sourceKey uniqueID:uniqueID] autorelease];
Depending on whether you are manipulating a single object or a range of objects with your command, you return the 'uniqueIDSpecifier' directly or add it to an array of consecutive such specifiers that are returned after the last addition. To support the implied 'get' command, you post a 'uniqueID' object specifier in each of your managed object entity sub-classes. The following specifier supports my 'Unit' class sub-class:
- (NSScriptObjectSpecifier *)objectSpecifier {
NSScriptObjectSpecifier *containerRef = [[NSApp delegate]levelsSpecifier];
NSString *uniqueID = [[[self objectID] URIRepresentation] absoluteString]; // This is the key method for determining the object's 'uniqueID'
if (uniqueID) {
NSScriptObjectSpecifier *uniqueIDSpecifier = [[[NSUniqueIDSpecifier allocWithZone: [self zone]] initWithContainerClassDescription:[containerRef keyClassDescription] containerSpecifier:containerRef key:@"unitsArray" uniqueID:uniqueID] autorelease];
[[NSApp delegate] setUnitsSpecifier:uniqueIDSpecifier]; // Post specifier so Units class specifier can access it
return uniqueIDSpecifier;
} else {
return nil;
}
Note the second commented line indicate that I post the results of this specifier in a global variable so that the object specifier of contained classes can use this specifier result as its container specifier. The application delegate is the one place all entity sub-classes can have access to application-wide accessors and methods such as this object specifier result.
I do hope this helps someone out there like myself, last month.