Реализация действия Automator на Objective-C

Особым преимуществом действий написанных на Objective-C (по сравнению с AppleScript), является то, что они имеют более широкий доступ к ряду системных ресурсов OS X. Действия, которые используют Objective-C код могут включать в себя функции не только Objective-C библиотек таких как Foundation и Application Kit, но практически любые другие библиотеки (поскольку Objective-C является расширением ANSI C, Objective-C класс может вызывать функции задекларированные в интерфейсе C). Чисто Objective-C действия имеют компенсирующий недостаток: они не могут легко управлять приложениями или получать доступ к их услугам, за исключением тех приложений, которые предлагают общедоступные API.

В целом, данные, которые Automator пропускает через действия рабочего процесса считаются AppleScript-совместимыми. Если он обнаруживает, что действие основано на Objective-C, Automator преобразует их в Objective-C объекты, с которыми действие может взаимодействовать. Он также принимает объекты, Objective-C действий и преобразует их в форму, пригодную для действий на основе AppleScript.

Указание идентификаторов типа Cocoa

Действие, которое основывается исключительно на Objective-C должны иметь Cocoa-определенный тип идентификаторов для своих свойств AMAccepts и AMProvides. В настоящее время существуют три общих Cocoa типа идентификаторов:

  • com.apple.cocoa.string - NSString для текста.
  • com.apple.cocoa.path - NSString для полного пути файловой системы.
  • com.apple.cocoa.url - NSURL для URL.

Установка свойств AMAccepts и AMProvides в редакторе Xcode:

Automator установка AMAccepts и AMPrivides

Например, если вы указали com.apple.cocoa.path как единственный идентификатор типа свойства AMAccepts, входной объект в метод runWithInput:fromAction:error: будет объектом NSString представляющим путь (или массив таких строковых объектов). Если вы указали com.apple.cocoa.url как идентификатор типа свойства AMProvides, вам придется возвращать объект NSURL (или массив объектов NSURL) в вашей реализации runWithInput:fromAction:error:.

Реализация runWithInput:fromAction:error:

В пользовательских подклассах AMBundleAction необходимо переопределить метод runWithInput:fromAction:error: (наследуется от AMAction класса). Помимо указания типа идентификаторов Cocoa в свойствах AMAccepts и AMProvides, реализация этого метода является единственным требованием для чисто Objective-C действия.

Если действию не приходится иметь дело с входными данными переданными ему, например, его роль заключается в выборе некоторых пунктов в файловой системе, реализация runWithInput:fromAction:error: может вернуть все, что предназначен для выходных данных, не касаясь входных. В примере из листинга ниже, действие возвращает список путей к выбраным фильмам в пользовательском интерфейсе (и хранимым в его parameters):

- (id)runWithInput:(id)input fromAction:(AMAction *)anAction
        error:(NSDictionary **)errorInfo {
    return [[self parameters] objectForKey:@"movieFiles"];
}

Тем не менее, большинство действий реагируют на входные данные переданные им от предыдущего действия. Объект входа и выхода для действия почти всегда NSArray объект, поскольку контейнер подсвойства AMAccepts и AMProvides, по умолчанию списки (эквивалент NSArray объектов в Objective-C). Многие Objective-C действия реализуют runWithInput:fromAction:error: используя общий подход, который похож на типичный on run обработчик действия на основе AppleScript:

  • Если контейнер типа свойства AMProvides является списком, то необходимо подготовить выходной массив для дальнейшего использования (как правило, путем создания NSMutableArray объекта).
  • Извлекайте элементы входного массива и для каждого выполните любую необходимую ему операцию, после запишите полученные данные в выходной массив.
  • Возвратите выходной массив.

Пример реализации действия на Objective-C

Создадим новый проект в Xcode, Automator > Cocoa.

Назовем его "Text Case Converter". Наше действие будет принимать на вход путь к текстовому файлу и выдавать на выходе его преобразованную версию, путем изменения регистра в виде текста.

Внимание: Если Xcode выдаст предупреждение о том, что Garbage Collection устарело и просит перестроить проект под использование ARC, не делайте этого, так, как (как это не странно) Automator не примет ваше действие.

Назначим свойствам AMAccepts: com.apple.cocoa.path и AMProvides - com.apple.cocoa.string, как показано в "Установка свойств AMAccepts и AMProvides". Установим категорию Text.

Далее редактируем схему запуска, как описано в "Тестирование, отладка и инсталяция действия".

Теперь добавим параметр, который будет указывать действию как преобразовывать строки из загруженного файла. Назовем его direction и назначим тип integer:

Automator Add Parameters Xcode

Переходим к файлу main.xib и приводим его к следующему виду (путем добавления элемента управления Pop Up Button:

Automator Objective-C Action main.xib

Отредактируем пункты меню Pop Up Button, назвав их UPPERCASE, lowercase и Capitalized. Далее выделим Pop Up Button, перейдем в Bindings Inspector и свяжем свойство Selected Index с объектом Parameters и введем в поле Model Key Path наш добавленный параметр direction.

Automator Actoin create Bindings

Откроем заголовочный файл "Text_Case_Converter.h" и объявим метод - (NSString*) convert:(NSString*)s to:(NSInteger)direction;, который собственно будет преобразовывать строки.

Теперь откроем "Text_Case_Converter.m" и отредактируем его следующим образом:

#import "Text_Case_Converter.h"

@implementation Text_Case_Converter

- (id)runWithInput:(id)input fromAction:(AMAction *)anAction error:(NSDictionary **)errorInfo
{
  NSInteger len = [input count];
	NSMutableArray* output = [NSMutableArray arrayWithCapacity:len];
  NSInteger direction = [[[self parameters] valueForKey:@"direction"] integerValue];
  @try {
    for(NSInteger i=0;i<len;i++){
      //Перечисляем входные параметры
      NSString* s = [NSString stringWithString:[input objectAtIndex:i] ];
      [output addObject:[self convert:s to:direction]];
    }
  }
  @catch (NSException *exception) {
    [output addObject:exception.reason];
  }
	return output;
}


- (NSString*) convert:(NSString*)s to:(NSInteger)direction
{
  NSError* error;
  NSString* text = [[NSString alloc] initWithContentsOfFile:s encoding:NSUTF8StringEncoding error:&error];
  if(!text){
    //если переданный на вход файл не является текстовым, выводим сообщенение об ошибке.
    NSException* exception = [NSException exceptionWithName:@"Error Text File"
         reason:error.value userInfo:error.userInfo];
    @throw exception;
  }
  NSString* res;
  if(!direction)
    res = [text uppercaseString];
  else if(direction==1)
    res = [text lowercaseString];
  else
    res = [text capitalizedString];
  return res;
}
@end

Теперь запустим наш проект. Если вы правильно отредактировали схему запуска, то запустится Automator и загрузит наше действие, которое появится в выбранной нами категории ("Текст").

Составим цепочку действий в Automator, примерно так:

Automator example action text converter

Запустим рабочий процесс в Automator, в результате которого, если мы выбрали в Finder файл он преобразуется, при условии, что он текстовый и откроется Text Edit, с новым содержимым файла или сообщением об ошибке.

Если ваши действия сталкивается с ошибкой, которая мешает ему продолжать работу, оно должно дать информацию, описывающую ошибку в Automator, который затем прекращает выполнение рабочего процесса и выводит сообщение об ошибке в оповещении. Последний параметр метода runWithInput:fromAction:error: является указателем на NSDictionary объект. Сообщая об ошибках, необходимо создать словарь, содержащий две пары ключ-значение и вернуть этот объект для Automator косвенно. Этими двумя свойствами словаря являются:

  • NSAppleScriptErrorNumber - Принимает номер ошибки инкапсулированный как объект NSNumber.
  • NSAppleScriptErrorMessage - Принимает объект NSString, содержащий описание ошибки.

Таким образом можно переписать @catch обработчик исключения в вышеприведенном примере:

@catch (NSException *exception) {
    NSArray *objsArray = [NSArray arrayWithObjects:
                         [NSNumber numberWithInt:errOSASystemError],
                         [NSString stringWithFormat:@"ERROR: %@\n", exception.reason], nil];
                           
    NSArray *keysArray = [NSArray arrayWithObjects:NSAppleScriptErrorNumber,
                                                 
                                                 NSAppleScriptErrorMessage, nil];
                           
    *errorInfo = [NSDictionary dictionaryWithObjects:objsArray forKeys:keysArray];
                           
    return nil;
}

Действия на основе Objective-C могут реализовывать любые возможности, доступные в Cocoa и C-API, но поскольку Objective-C действия идут в дополнительном потоке, если они хотят отобразить окно, то это должно быть сделано в основном потоке. Метод performSelectorOnMainThread:withObject:waitUntilDone: является достаточным для этой цели, например:

[self performSelectorOnMainThread:@selector(showPreviewWithData:) withObject:data waitUntilDone:YES];

Обновление параметров действия

Обычно действие использует технологию Cocoa bindings для синхронизации настроек пользовательского интерфейса действия с параметрами действия. Но действию не требуется использование bindings-привязки, чтобы выполнить синхронизацию. Это можно сделать вручную, путем реализации методов updateParameters и parametersUpdated. Эти методы вызываются, когда пришла пора запустить или сохранить действие.

Метод updateParameters вызывается когда атрибут параметры действия (NSDictionary объекта) должны быть обновлены из настроек и значений, которые пользователь указал в пользовательском интерфейсе. Метод parametersUpdated вызывается по противоположной причине: когда необходимо всплывающие элементы, кнопки, текстовые поля и другие объекты, привести в состояние соответствующее с текущим содержимым параметров действия, особенно, когда эти изменения производились программно.

- (void)updateParameters
{
    // text
    NSString *outputFile = [_outputFilePath stringValue];
    if (outputFile)
    {
        [[self parameters] setObject:outputFile forKey:@"outputFilePath"];
    }

    NSString *waitSeconds = [_waitSeconds stringValue];
    if (waitSeconds)
    {
        [[self parameters] setObject:[NSNumber numberWithInt:
            [_waitSeconds intValue]] forKey:@"waitSeconds"];
    }

    // radios
    [[self parameters] setObject:[NSNumber numberWithInt:
        [_interactiveType selectedRow]] forKey:@"interactiveType"];
    [[self parameters] setObject:[NSNumber numberWithInt:
        [_screenShotType indexOfSelectedItem]] forKey:@"screenshotType"];

    // popup
    [[self parameters] setObject:[NSNumber numberWithInt:
        [_outputType indexOfSelectedItem]] forKey:@"outputType"];

    // checks
    [[self parameters] setObject:[NSNumber numberWithBool:
        [_captureMainMonitorOnly state]] forKey:@"captureMainMonitorOnly"];
    [[self parameters] setObject:[NSNumber numberWithBool:
        [_disableSounds state]] forKey:@"disableSounds"];
    [[self parameters] setObject:[NSNumber numberWithBool:
        [_timedScreenshot state]] forKey:@"timedScreenshot"];
}
 
 
homeЗаметили ошибкукарта сайта 
   Made on a Mac