Как поддерживать элемент AppleScript (отношение "один ко многим") класса приложения (NSApp)?
Вот что у меня есть:
1) Включите соответствующие "разрешения" записи в "Info.plist
- "Сценарий": ДА
- "Имя файла определения сценария": myApp.sdef
2) Включите тег "element" элемента в расширение "element" расширения класса:
`<class-extension extends="application" description="The application and top-level scripting object.">
<!-- various property tags go here -->
<element type="object item" access="r">
<cocoa key="theseObjects"/>
</element>
</class-extension>`
3) Включите тег класса элемента:
<class name="object item" code="Objs" description="Application 'too many' object collection" plural="object items" inherits="item"> // I don't believe 'inherits' name is critical for AS to work
<cocoa class="ObjectItem"/>
</class>
4) Включите метод делегата, который перенаправляет поддержку сценариев "NSApplication" своему делегату:
- (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key {
if ([key isEqualToString:@"theseObjects"]) {
return YES;
}
return NO;
}
5) Создайте класс ObjectItem и поместите туда спецификатор объекта:
- (NSScriptObjectSpecifier *)objectSpecifier {
NSScriptObjectSpecifier *containerRef = nil;
NSScriptObjectSpecifier *specifier = [[NSNameSpecifier alloc] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:containerRef key:@"theseObjects" name:@"objectName"];
return [specifier autorelease];
6) Разместите метод доступа KVO в делегате Приложения:
- (NSArray *)theseObjects;
{
ObjectItem *thisObject = [[ObjectItem new] autorelease];
NSArray *thisArray = [NSArray arrayWithObject:thisObject];
return thisArray;
}
}
7) Создайте AppleScript, который возвращает объекты из моего метода получения элемента:
tell application "SpellAnalysis"
get theseObjects
end tell
8) Результат: ошибка "Переменные объекты не определены". номер -2753 из "объектов"
9) Потяни за волосы
2 ответа
Что касается оригинального поста "Один ко многим", автор проделал хорошую работу, изложив шаги, чтобы добраться до точки, где сценарии почти работают. К счастью, для решения проблемы требуется всего несколько небольших изменений:
1) Образец AppleScript
tell application "SpellAnalysis" to get theseObjects
не может работать AS не может ссылаться на имена переменных Objective C (thisObjects). Так как sdef определяет класс и тип элемента "object item", это то, что должно использоваться в скрипте:
tell application "SpellAnalysis" to get object items -- FIX #1
2) Спецификатор объекта в классе ObjectItem должен возвращать фактическое значение свойства name текущего объекта, а не строку @"objectName". Так и должно быть:
- (NSScriptObjectSpecifier *)objectSpecifier
{
NSScriptObjectSpecifier *containerRef = nil;
NSScriptObjectSpecifier *specifier = [[NSNameSpecifier alloc]
initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]]
containerSpecifier:containerRef
key:@"theseObjects"
name:self.name]; // FIX #2
return specifier;
}
3) Полный и правильный sdef для поддержки вышесказанного:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
<dictionary xmlns:xi="http://www.w3.org/2003/XInclude">
<xi:include href="file:///System/Library/ScriptingDefinitions/CocoaStandard.sdef" xpointer="xpointer(/dictionary/suite)"/>
<suite name="SpellAnalysis Suite" code="sSIA" description="ToDo">
<class name="object item" code="Objs" description="ToDo" plural="object items">
<cocoa class="ObjectItem"/> <!-- KVC: Objective-C class ObjectItem : NSObject -->
<property name="name" code="pnam" type="text" access="r" description="Its name.">
<cocoa key="name"/>
</property>
</class>
<class-extension name="SpellAnalysis application" extends="application" description="ToDo">
<element type="object item" access="r">
<cocoa key="theseObjects"/> <!-- KVC: app delegate's @property NSArray* theseObjects; -->
</element>
</class-extension>
</suite>
</dictionary>
4) Интерфейс ObjectItem.h для поддержки вышеуказанного:
@interface ObjectItem : NSObject
@property (nonatomic, strong) NSString *name;
- (instancetype)initWithName:(NSString *)name;
@end
5) Файл ObjectItem.m для поддержки вышеуказанного:
#import "ObjectItem.h"
@implementation ObjectItem
- (instancetype)initWithName:(NSString *)name
{
if (self = [super init])
{
self.name = name;
}
return self;
}
- (NSScriptObjectSpecifier *)objectSpecifier
{
NSScriptObjectSpecifier *containerRef = nil;
NSScriptObjectSpecifier *specifier = [[NSNameSpecifier alloc]
initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]]
containerSpecifier:containerRef
key:@"theseObjects"
name:self.name]; // FIX #2
return specifier;
}
@end
6) Наконец, код в вашем классе делегата приложения для поддержки вышеуказанного:
#import "MyAppDelegate.h"
#import "ObjectItem.h"
@interface MyAppDelegate ()
@property (nonatomic, strong) NSArray *theseObjects;
@end
@implementation MyAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.theseObjects = @[
[[ObjectItem alloc] initWithName:@"Item A"],
[[ObjectItem alloc] initWithName:@"Item B"],
[[ObjectItem alloc] initWithName:@"Item C"]
];
}
- (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key
{
if ([key isEqualToString:@"theseObjects"])
{
return YES;
}
return NO;
}
@end
Вот и все, просто исправим пару простых ошибок (одну в скрипте, одну в коде спецификатора объекта), и следующий скрипт вернет ожидаемые результаты:
tell application "SpellAnalysis"
get object items -- expect a list of object references for all objects in array
end tell
--> {object item "Item A" of application "SpellAnalysis", object item "Item B" of application "SpellAnalysis", object item "Item C" of application "SpellAnalysis"}
Другие скрипты также работают:
tell application "SpellAnalysis"
get object item 2 -- expect a object reference to the second item in the array
end tell
--> object item "Item B" of application "SpellAnalysis"
tell application "SpellAnalysis"
name of object item 2 -- Expected result: the name property of 2nd obj in array
end tell
--> "Item B"
Я подумал, что мне следует опубликовать некоторую дополнительную информацию о поддержке сценариев Cocoa для приложений с основными данными, поскольку там так мало информации. Я потратил минимум месяц, пытаясь понять, как с этим механизмом договориться.
Хотя я смог обеспечить поддержку AppleScript для своего приложения с основными данными, используя указатели индекса, я обнаружил, что использование спецификаторов "uniqueID" обеспечивает лучшее соответствие. Лучше, потому что отношения базовых данных со многими поддерживаются неупорядоченными наборами, а не массивами. Базовые данные позволяют указать управляемый объект по идентификатору объекта, который можно оценить с помощью метода сценариев Какао. Тем не менее, для реализации поддержки отношения "слишком много" с использованием спецификаторов uniqueID необходимы дополнительные элементы кода.
1) Укажите элемент свойства в 'sdef' для каждой сущности, которую вы будете поддерживать. Например, чтобы поддержать мою сущность 'level', я размещаю следующее в тегах класса 'level':
<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>
Обратите внимание, что тип обозначается как "текст", а не как "спецификатор". Обмен 'uniqueID' между механизмом AppleEvent и поддержкой AppleScript Какао будет строковым значением. Также обратите внимание, что значение "ключ какао" равно "uniqueID". Этот ключ (типичный для таких ключей в 'sdef') используется поддержкой сценариев AppleScript для идентификации имени метода в вашем приложении, которое соответствует шаблону KVC.
2) Опубликовать метод valueInWithUniqueID в классе, который содержит целевые объекты. Именно в этом методе вы предоставляете способ извлечения управляемого объекта, соответствующего любому уникальному идентификатору, переданному методу. Вот мой метод KVO 'valuesArray', размещенный в моем классе контейнера 'Уровни'.
- (id)valueInLevelsArrayWithUniqueID:(NSString *)uniqueID;
{
NSManagedObject *managedObject= [[[NSApp delegate] myManagedObjectContext] objectWithID:[[[NSApp delegate] lessonsManager] managedObjectIDForURIRepresentation:[NSURL URLWithString:uniqueID]]];
return managedObject;
}
А вот мое объявление свойства 'sdef' для содержащегося класса 'unit':
<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>
Мой класс 'Units' также вызывает метод value. Обратите внимание на основной шаблон имени KVC:
- (id)valueInUnitsArrayWithUniqueID:(NSString *)uniqueID;
{
NSManagedObject *managedObject= [[[NSApp delegate] lessonsDBase] objectWithID:[[[NSApp delegate] lessonsManager] managedObjectIDForURIRepresentation:[NSURL URLWithString:uniqueID]]];
return managedObject;
}
С их помощью, если моему AppleScript потребуется указать объект 'level' из его 'uniqueID', он получит ответ. Это, кажется, вступает в игру, когда у вас есть иерархия классов сущностей, и вы пишете AppleScript, который 1) вызывает команду, которая возвращает ссылку на свой результат, и 2) воздействует на этот возвращенный результат другой командой:
count (make new section at unit 1 of level 1)
Любопытно, что следующее не вызывает метод значения:
count (unit 1 of level 1)
Обратите внимание, что в нем отсутствует условие 1- отсутствует основная команда (например, "make"). В этом случае вызывается неявная команда AppleScript 'get', однако, кажется, что сценарии Cocoa определяют значения иерархии объектов другим способом.
3) Предоставить спецификаторы объекта "uniqueID" для всех объектов, возвращаемых из команд, которые вы поддерживаете, а также явно названный "objectSpecifier" для каждого из подклассов сущности, которые поддерживают их подразумеваемые команды "get". Например, моя команда "clone", размещенная в ее "executeDefaultImplementation", предоставляет следующий метод:
NSUniqueIDSpecifier *uniqueIDSpecifier = [[[NSUniqueIDSpecifier allocWithZone:[self zone]] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:sourceContainerSpecifier key:sourceKey uniqueID:uniqueID] autorelease];
В зависимости от того, манипулируете ли вы одним объектом или диапазоном объектов с помощью вашей команды, вы возвращаете "uniqueIDSpecifier" напрямую или добавляете его в массив последовательных таких спецификаторов, которые возвращаются после последнего добавления. Чтобы поддержать подразумеваемую команду "get", вы публикуете спецификатор объекта "uniqueID" в каждом из ваших подклассов сущности управляемого объекта. Следующий спецификатор поддерживает мой подкласс класса 'Unit':
- (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;
}
Обратите внимание, что вторая закомментированная строка указывает, что я публикую результаты этого спецификатора в глобальной переменной, чтобы спецификатор объекта содержащихся классов мог использовать этот результат спецификатора в качестве своего спецификатора контейнера. Делегат приложения - это единственное место, где все подклассы сущности могут иметь доступ к методам доступа и методам, таким как результат этого спецификатора объекта.
Я надеюсь, что это поможет кому-то вроде меня, в прошлом месяце.