Используя сценарии моста

Сценарии кода Моста очень походят на любой другой код Objective C. Однако существуют некоторые различия, главным образом происходящие из архитектуры OSA, на которой базируются отгрузка и обработка событий Apple. В этой главе описываются, как использовать Мост Сценариев в проектах Objective C, указывая на те различия по пути. Улучшение Производительности Сценариев Кода Моста также обсуждает корректное или оптимальное использование Сценариев Моста, но от угла производительности.

Подготовка кодировать

Прежде чем Вы начнете писать любой код Моста Сценариев для своего проекта, существует несколько шагов, которые необходимо завершить:

  1. Генерируйте заголовочные файлы для всех scriptable заявлений, в которые Ваш код посылает сообщения.

  2. Добавьте эти файлы к своему проекту.

  3. В Вашем заголовке или файлах реализации, добавить #import операторы для сгенерированных заголовочных файлов.

  4. Добавьте платформу Моста Сценариев к своему проекту.

Можно использовать id когда Вы разрабатываете свой проект, вводить объекты Моста Сценариев динамично, но такой код уязвимо для программных ошибок и приводит к большому количеству предупреждающих сообщений. Рекомендуется генерировать заголовочный файл для каждого scriptable приложения, добавьте, что эти файлы к проекту, и в коде дают определенные типы Моста Сценариев на основе того, что Вы находите в заголовочных файлах. Выполнение так позволяет компилятору проверять типы объектов (устраняющий побочные предупреждающие сообщения) и дает Вам больше обеспечения, что Вы отправляете сообщения корректным получателям.

Заголовочный файл, который Вы генерируете для scriptable приложения, служит справочной документацией для классов сценариев того приложения. Это включает информацию об отношениях наследования между классами и отношениях включения между их объектами. Это также показывает, как объявляются команды, свойства и элементы. Беря приложение iTunes в качестве примера, заголовочный файл показывает определение класса приложений (iTunesApplication), классы сценариев приложения (такой как iTunesTrack и iTunesSource), команды (такой как eject метод), и свойства (такой как artist заявленное свойство). Заголовочный файл также включает комментарии, извлеченные из определения сценариев, такие как комментарий, добавленный к этому объявлению для FinderApplication класс:

- (void)empty;   // Empty the trash

Для создания заголовочного файла необходимо выполнить два инструмента командной строки —sdef и sdp— вместе, с выводом от одного переданного по каналу до другого. Это - рекомендуемый синтаксис:

sdef /path/to/application.app | sdp -fh --basename applicationName

sdef утилита получает определение сценариев из определяемого приложения; если то приложение не содержит sdef файл, но действительно вместо этого содержит информацию о сценариях в более старом формате (таком как комплект сценариев и списки свойств терминологии), это переводит ту информацию в формат sdef сначала. sdp инструмент, выполненный с вышеупомянутыми опциями, генерирует заголовочный файл Objective C для определяемого scriptable приложения. Таким образом, для iTunes, Вы выполнили бы следующую команду для создания названного заголовочного файла iTunes.h:

sdef /Applications/iTunes.app | sdp -fh --basename iTunes

Добавьте сгенерированный файл к своему проекту XCode путем выбора Add to Project из Меню проектов и указания файла в следующем диалоговом окне. В любом исходном или заголовочном файле в Вашем проекте, что ссылки, Пишущие сценарий объектов Моста, вставьте надлежащие #import операторы, такие как следующее:

#import "iTunes.h"

Наконец, удостоверьтесь, что Вы добавили платформу Моста Сценариев (/System/Library/Frameworks/ScriptingBridge.framework) к Вашему проекту с помощью Проекта> Добавляют к команде Меню проектов. Не необходимо иметь #import операторы для платформы, потому что заголовочные файлы для scriptable приложений уже делают это.

Создание объекта приложения

Прежде чем можно будет отправить сообщения в scriptable приложение, Вам нужен объект, представляющий приложение. Как объяснено в Классах Платформы Моста Сценариев, платформа Моста Сценариев объявляет три метода фабрики классов для создания экземпляров scriptable приложений; каждый принимает различное значение для определения местоположения приложения.

Рекомендуемый метод для создания экземпляра scriptable приложения applicationWithBundleIdentifier:. Метод может определить местоположение приложения в системе, даже если пользователь переименовал приложение, и это не требует, чтобы Вы знали, где приложение установлено в файловой системе (который мог быть где угодно). Следующая строка кода создает экземпляр приложения iTunes:

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

Если Вы не знаете идентификатора пакета приложения, можно найти его путем поиска значения CFBundleIdentifier свойство в Info.plist файл хранится в комплекте приложений.

Могли бы быть случаи при использовании одного из других методов фабрики классов, целесообразен. Например, если Вы пишете приложение, использующее Мост Сценариев для Вашего собственного персонального использования, Вы могли обратиться к приложениям URL. Следующий пример создает экземпляр приложения Страниц, определяя местоположение его URL в расположении установки кроме /Applications.

NSURL *pagesURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/Applications/iWork/Pages.app", NSHomeDirectory()]];
PagesApplication *pagesApp = [SBApplication applicationWithURL:pagesURL];

Управление приложением

Для управления scriptable приложением просто отправьте в экземпляр приложения или один из его объектов сообщение на основе метода, объявленного классом объекта. Эти методы соответствуют командам в определении сценариев приложения. Метод действия, перечисленный в Перечислении 2-1, играет в настоящее время выбираемый трек iTunes и затем модулирует объем звука, в конечном счете восстанавливая его к исходному уровню.

Перечисление 2-1  , Управляющее объемом iTunes

- (IBAction)play:(id)sender {
    iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
    if ( [iTunes isRunning] ) {
        int rampVolume, originalVolume;
        originalVolume = [iTunes soundVolume];
 
        [iTunes setSoundVolume:0];
        [iTunes playOnce:NO];
 
        for (rampVolume = 0; rampVolume < originalVolume; rampVolume += originalVolume / 16) {
            [iTunes setSoundVolume: rampVolume];
            /* pause 1/10th of a second (100,000 microseconds) between adjustments. */
            usleep(100000);
        }
        [iTunes setSoundVolume:originalVolume];
    }
}

Обратите внимание на то, что этот метод тестирует, работает ли приложение, прежде чем это попытается управлять им. ( isRunning метод объявляется SBApplication класс.) Эта практика программирования обсуждена более подробно в Улучшении Производительности Сценариев Кода Моста.

Получение и установка свойств

В графе объектов, что Пишущий сценарий Моста динамично генерирует для scriptable приложения, большинство объектов является контейнерами других объектов, или объектов, относящихся к другому объекту сценариев. В смысле моделирования данных они выражают - многие или - отношения и позволяют Вашему коду «выполнить развертку» графа объектов. Только, когда Вы добираетесь до вершин графика, обычно свойства объекта, что Вы в состоянии получить доступ к конкретным данным, таким как имя, цвет или численное значение. Как можно вспомнить, Писание сценарий Моста реализует свойства сценариев объектов как объявленные свойствами в Objective C.

Получение значения свойства требует, чтобы Вы переместились по иерархии объектов приложения, пока Вы не приезжаете в целевой объект — т.е. объект, объявляющий те свойства — и затем отправляющий сообщение в тот объект, совпадающий с именем свойства. Иногда Вы не должны перемещаться по этому далеко. Например, фрагмент кода в Перечислении 2-2 отправляет два сообщения в ITunesApplication объект.

Перечисление 2-2  , Получающее имя текущей дорожки

iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
NSLog(@"Current song is %@", [[iTunes currentTrack] name]);

Первое сообщение к приложению получает значение currentTrack свойство; это сообщение приводит к объекту класса iTunesTrack представление дорожки, в настоящее время играя. Этот объект самостоятельно не представляет конкретных данных, но сообщения, тогда отправленного в него (name) возвращает значение name свойство как NSString объект.

Можно также установить значения свойств, если они не (в их объявлении) отмечены как readonly. Код в Перечислении 2-3 реализует инструмент командной строки, очищающийся locked свойство в элементах в Мусоре.

Перечисление 2-3  , устанавливающее locked свойство элементов Средства поиска

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    FinderApplication *theFinder = [SBApplication applicationWithBundleIdentifier: @"com.apple.finder"];
    SBElementArray *trashItems = [[theFinder trash] items];
    if ([trashItems count] > 0) {
        for (FinderItem *item in trashItems) {
            if ([item locked]==YES)
                [item setLocked:NO];
        }
    }
    [pool drain];
    return 0;
}

Поскольку перечисление показывает, можно установить значение с сообщением формы setСвойство:, где Свойство является именем свойства с капитализируемой первой буквой. Можно также использовать запись через точку при установке значений свойств — это, в конце концов, Objective C, объявил свойства. Например, Вы могли установить значение locked свойство с этим оператором:

item.locked = NO;

Однако Вы могли переписать код Моста Сценариев в Перечислении 2-3, чтобы быть более эффективными при помощи setValue:forKey: метод NSArray. Пример в Перечислении 2-4 отправляет всего одно событие Apple вместо одного события на элемент в Мусоре.

Перечисление 2-4  Более эффективно установок locked свойство

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    FinderApplication *theFinder = [SBApplication applicationWithBundleIdentifier: @"com.apple.finder"];
    SBElementArray *trashItems = [[theFinder trash] items];
    [trashItems setValue:[NSNumber numberWithBool:NO] forKey:@"locked"];
    [pool drain];
    return 0;
}

Вместо сообщений можно использовать запись через точку для пересечения графа объектов scriptable приложения. Следующая строка эквивалентна второй строке в Перечислении 2-2:

NSLog(@"Current song is %@", iTunes.currentTrack.name);

Иногда выяснение у объекта сценариев для значения свойства не могло бы возвратить ожидаемые конкретные данные. Если само возвращенное значение является экземпляром специализированного подкласса, это происходит SBObject, и таким образом объектный спецификатор, относящийся к тому, что может быть конкретными данными. Рассмотрите пример в Перечислении 2-5. Этот код копирует текстовое содержание каждого выбранного Сообщения электронной почты к документу TextEdit.

Перечисление 2-5  , Вызывающее оценку сценариев, возражает для получения значения свойства

- (IBAction)copyMailMessage:(id)sender {
    MailApplication *mailApp = [SBApplication applicationWithBundleIdentifier:@"com.apple.mail"];
    TextEditApplication *textEdit = [SBApplication applicationWithBundleIdentifier:@"com.apple.TextEdit"];
    SBElementArray *viewers = [mailApp messageViewers];
    for (MailMessageViewer *viewer in viewers) {
        NSArray *selected = [viewer selectedMessages];
        for (MailMessage *message in selected) {
            TextEditDocument *doc = [[[textEdit classForScriptingClass:@"document"] alloc] init];
            [[textEdit documents] addObject:doc];
            doc.text = [[message content] get];
        }
    }
}

Последняя строка кода в примере включает сообщение [message content], но это сообщение не приводит к текстовому содержанию сообщения, как можно было бы ожидать. Вместо этого a get сообщение требуется, чтобы вызывать оценку и таким образом получать текст для присвоения. Что [message content] возвраты в экземпляре MailText, который подкласс SBObject. Этот экземпляр является объектным спецификатором, обращающимся к фактическому тексту. Для больше на протолкнутой оценке объектных спецификаторов get метод, посмотрите Улучшение Производительности Сценариев Кода Моста.

Используя массивы элемента

Массивы элемента, или SBElementArray объекты, наборы сценариев объектов того же типа. Сценарии Моста реализуют элементы, которые это находит в определении сценариев приложения как методы тот возврат SBElementArray экземпляры. Поскольку SBElementArray подкласс NSMutableArray, можно отправить методы в массивы элемента, объявляющиеся NSMutableArray и его суперкласс, NSArray. Например, как показано в Перечислении 2-6, Вы могли использовать NSEnumerator объект перечислить через массив.

Перечисление 2-6  Используя перечислитель для итерации через массив элемента

FinderApplication *theFinder = [[[SBApplication applicationWithBundleIdentifier: @"com.apple.finder"] alloc] init];
SBElementArray *trashItems = [[theFinder trash] items];
if ([trashItems count] > 0) {
    NSEnumerator *tren = [trashItems objectEnumerator];
    FinderItem *item;
    while (item = [tren nextObject]) {
        if ([item locked]==YES)
            [item setLocked:NO];
    }
}

Или Вы могли даже использовать быструю функцию перечисления Objective C 2.0, чтобы более эффективно выполнить итерации через массив, как показано в Перечислении 2-7.

Перечисление 2-7  Используя быстрое перечисление на массиве элемента

FinderApplication *theFinder = [[[SBApplication applicationWithBundleIdentifier: @"com.apple.finder"] alloc] init];
    SBElementArray *trashItems = [[theFinder trash] items];
    if ([trashItems count] > 0) {
        for (FinderItem *item in trashItems) {
            item.locked = NO;
        }
    }

Несмотря на то, что возможно перечислить через SBElementArray объекты, как правило не перечисляйте, если можно избежать его. Можно получить намного лучшую производительность при помощи одной из “объемной работы” методы массива, которые обеспечивает Какао и Пишущий сценарий Моста:

Код в Перечислении 2-8 использует последний из этих методов для создания раскрывающегося списка с календарными именами, полученными из приложения iCal.

Перечисление 2-8  Используя arrayByApplyingSelector: метод для заполнения кнопки всплывающего меню

- (void)awakeFromNib {
 
    [time setDateValue: [NSDate date]];
    [NSApp setDelegate: self];
    iCalApplication *iCal = [SBApplication applicationWithBundleIdentifier:@"com.apple.iCal"];
    SBElementArray *calendars = [iCal calendars];
    [cal_popup removeAllItems]; // cal_pop is an outlet to a pop-up button
    [cal_popup addItemsWithTitles:[calendars arrayByApplyingSelector: @selector(name)]];
}

Фрагмент кода в Перечислении 2-9 (который извлечен из метода, показанного в Перечислении 2-11) использует filteredArrayUsingPredicate: метод для создания массива, содержащего подмножество календарных событий, что у каждого есть определенное имя.

Перечисление 2-9  , Фильтрующее массив элемента с помощью предиката

iCalEvent *theEvent;
NSArray *matchingEvents = [[theCalendar events] filteredArrayUsingPredicate:
            [NSPredicate predicateWithFormat:@"summary == %@", eventName]];
if ( [matchingEvents count] >= 1 ) {
        // handle any events that match..
}

SBElementArray также объявляет методы для извлечения отдельных объектов от массивов элемента с помощью различных форм ссылки: имя (objectWithName:), уникальный идентификатор (objectWithName:), и расположение в массиве (objectAtLocation:). Фрагмент кода в Перечислении 2-10 показывает использование objectWithName: метод. (Снова, этот фрагмент взят от примера в Перечислении 2-11.)

Перечисление 2-10  , Получающее объект от элемента, выстраивает путем указания его имени

iCalApplication *iCal = [[[SBApplication applicationWithBundleIdentifier:@"com.apple.iCal"] alloc] init];
iCalCalendar *theCalendar;
 
[iCal activate];
 
NSString *calendarName = [calendar titleOfSelectedItem];
 
if ( [[[iCal calendars] objectWithName:calendarName] exists] ) {
    theCalendar = [[iCal calendars] objectWithName:calendarName];
}
// etc....

Для добавления сценариев возражает против массивов элемента, можно использовать NSMutableArray методы такой как insertObject:atIndex: и addObject:. Для удаления объектов из массивов элемента вызвать removeObject: (или связанный метод NSMutableArray) на массиве элемента.

Создание и добавление пишущих сценарий объектов

В дополнение к разрешению Вы управлять приложением и добраться и установить свойства сценариев объектов, Пишущий сценарий Моста позволяете Вам добавить, что сценарии возражают против приложения. Например, Вы могли использовать Мост Сценариев для динамичного добавления списка воспроизведения к iTunes. Существует две важных инструкции для запоминания при добавлении объекта сценариев к приложению:

Можно использовать init и initWithProperties: методы SBObject инициализировать объект сценариев. Вот несколько строк кода (взяты от Перечисления 2-5) то использование init:

TextEditDocument *doc = [[[textEdit classForScriptingClass:@"document"] alloc] init];
[[textEdit documents] addObject:doc];
doc.text = [[message content] get];

Обратите внимание на то, что код не устанавливает text свойство до объекта сценариев было добавлено к массиву элемента TextEdit документов. Следующий фрагмент кода, с другой стороны, создает словарь с текстовым свойством и затем инициализирует объект сценариев с ним использование initWithProperties: :

NSDictionary *props = [NSDictionary dictionaryWithObject:[[message content] get] forKey:@"text"];
TextEditDocument *doc = [[[textEdit classForScriptingClass:@"document"] alloc] initWithProperties:props];
[[textEdit documents] addObject:doc];

Перечисление 2-11 обеспечивает расширенный пример условного создания и вставки iCal calendar и event объекты, показывая оба подхода.

  Создание перечисления 2-11 и добавление новых сценариев возражают против графа объектов

 
- (IBAction)addUpdateEvent:(id)sender {
 
    iCalApplication *iCal = [SBApplication applicationWithBundleIdentifier:@"com.apple.iCal"];
    iCalCalendar *theCalendar;
 
    [iCal activate];
 
    NSString *calendarName = [calendar titleOfSelectedItem];
 
    if ( [[[iCal calendars] objectWithName:calendarName] exists] ) {
        theCalendar = [[iCal calendars] objectWithName:calendarName];
    } else {
 
        NSDictionary *props = [NSDictionary dictionaryWithObject:calendarName forKey:@"name"];
        theCalendar = [[[[iCal classForScriptingClass:@"calendar"] alloc] initWithProperties: props] autorelease];
        [[iCal calendars] addObject: theCalendar];
    }
 
    NSString *eventName = [event stringValue];
    NSDate* startDate = [time dateValue];
    NSDate* endDate = [[[NSDate alloc] initWithTimeInterval:3600 sinceDate:startDate] autorelease];
 
    iCalEvent *theEvent;
    NSArray *matchingEvents =
        [[theCalendar events] filteredArrayUsingPredicate:
            [NSPredicate predicateWithFormat:@"summary == %@", eventName]];
 
    if ( [matchingEvents count] >= 1 ) {
        theEvent = (iCalEvent *) [matchingEvents objectAtIndex:0];
        [theEvent setStartDate:startDate];
        [theEvent setEndDate:endDate];
    } else {
        theEvent = [[[[iCal classForScriptingClass:@"event"] alloc] init] autorelease];
        [[theCalendar events] addObject: theEvent];
        [theEvent setSummary:eventName];
        [theEvent setStartDate:startDate];
        [theEvent setEndDate:endDate];
    }
    [iCal release];
}