Какао будет плавно преобразовывать NSDictionary
объекты в записи AppleScript (AS) и наоборот, вам нужно только сказать ему, как это сделать.
Прежде всего вам необходимо определить record-type
в файле определения сценария (.sdef
), например,
<record-type name="http response" code="HTRE">
<property name="success" code="HTSU" type="boolean"
description="Was the HTTP call successful?"
/>
<property name="method" code="HTME" type="text"
description="Request method (GET|POST|...)."
/>
<property name="code" code="HTRC" type="integer"
description="HTTP response code (200|404|...)."
>
<cocoa key="replyCode"/>
</property>
<property name="body" code="HTBO" type="text"
description="The body of the HTTP response."
/>
</record-type>
name
- это имя, которое будет иметь это значение в записи AS. Если имя равно ключу NSDictionary
, тег <cocoa>
не требуется (success
, method
, body
в приведенном выше примере), если нет, вы можете использовать тег <cocoa>
, чтобы сообщить Какао правильный ключ для чтения этого значения (в приведенном выше примере code
- это имя в записи AS, но в NSDictionary
ключ будет replyCode
вместо этого; я только что сделал это для демонстрационных целей).
Очень важно сообщить Какао, какой тип AS должен иметь это поле, иначе Какао не знает, как преобразовать это значение в значение AS. Все значения являются необязательными по умолчанию, но если они присутствуют, они должны иметь ожидаемый тип. Вот небольшая таблица того, как наиболее распространенные типы Foundation соответствуют типам AS (неполные):
AS Type | Foundation Type
-------------+-----------------
boolean | NSNumber
date | NSDate
file | NSURL
integer | NSNumber
number | NSNumber
real | NSNumber
text | NSString
См. Таблица 1-1 Apple " Введение в руководство по написанию сценариев какао * "
Конечно, само значение может быть другой вложенной записью, просто определите для нее record-type
, используйте имя record-type
в спецификации property
, а в NSDictionary
значение должно быть соответствующим словарем .
Хорошо, давайте попробуем полный пример. Давайте определим простую команду HTTP get в нашем файле .sdef
:
<command name="http get" code="httpGET_">
<cocoa class="HTTPFetcher"/>
<direct-parameter type="text"
description="URL to fetch."
/>
<result type="http response"/>
</command>
Теперь нам нужно реализовать эту команду в Obj-C, которая очень проста:
#import <Foundation/Foundation.h>
// The code below assumes you are using ARC (Automatic Reference Counting).
// It will leak memory if you don't!
// We just subclass NSScriptCommand
@interface HTTPFetcher : NSScriptCommand
@end
@implementation HTTPFetcher
static NSString
*const SuccessKey = @"success",
*const MethodKey = @"method",
*const ReplyCodeKey = @"replyCode",
*const BodyKey = @"body"
;
// This is the only method we must override
- (id)performDefaultImplementation {
// We expect a string parameter
id directParameter = [self directParameter];
if (![directParameter isKindOfClass:[NSString class]]) return nil;
// Valid URL?
NSString * urlString = directParameter;
NSURL * url = [NSURL URLWithString:urlString];
if (!url) return @{ SuccessKey : @(false) };
// We must run synchronously, even if that blocks main thread
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
if (!sem) return nil;
// Setup the simplest HTTP get request possible.
NSURLRequest * req = [NSURLRequest requestWithURL:url];
if (!req) return nil;
// This is where the final script result is stored.
__block NSDictionary * result = nil;
// Setup a data task
NSURLSession * ses = [NSURLSession sharedSession];
NSURLSessionDataTask * tsk = [ses dataTaskWithRequest:req
completionHandler:^(
NSData *_Nullable data,
NSURLResponse *_Nullable response,
NSError *_Nullable error
) {
if (error) {
result = @{ SuccessKey : @(false) };
} else {
NSHTTPURLResponse * urlResp = (
[response isKindOfClass:[NSHTTPURLResponse class]] ?
(NSHTTPURLResponse *)response : nil
);
// Of course that is bad code! Instead of always assuming UTF8
// encoding, we should look at the HTTP headers and see if
// there is a charset enconding given. If we downloaded a
// webpage it may also be found as a meta tag in the header
// section of the HTML. If that all fails, we should at
// least try to guess the correct encoding.
NSString * body = (
data ?
[[NSString alloc]
initWithData:data encoding:NSUTF8StringEncoding
]
: nil
);
NSMutableDictionary * mresult = [
@{ SuccessKey: @(true),
MethodKey: req.HTTPMethod
} mutableCopy
];
if (urlResp) {
mresult[ReplyCodeKey] = @(urlResp.statusCode);
}
if (body) {
mresult[BodyKey] = body;
}
result = mresult;
}
// Unblock the main thread
dispatch_semaphore_signal(sem);
}
];
if (!tsk) return nil;
// Start the task and wait until it has finished
[tsk resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return result;
}
Конечно, возвращение nil
в случае внутренних сбоев - плохая обработка ошибок. Мы могли бы вернуть ошибку вместо. Ну, есть даже специальные методы обработки ошибок для AS, которые мы могли бы использовать здесь (например, установка определенных свойств, которые мы унаследовали от NSScriptCommand
), но это всего лишь пример.
Наконец, нам нужен код AS для его проверки:
tell application "MyCoolApp"
set httpResp to http get "http://badserver.invalid"
end tell
Результат:
{success:false}
Как и ожидалось, теперь тот, который преуспевает:
tell application "MyCoolApp"
set httpResp to http get "http://stackoverflow.com"
end tell
Результат:
{success:true, body:"<!DOCTYPE html>...", method:"GET", code:200}
Также, как и ожидалось.
Но подождите, вы хотели это наоборот, верно? Хорошо, давайте попробуем это тоже. Мы просто повторно используем наш тип и создаем другую команду:
<command name="print http response" code="httpPRRE">
<cocoa class="HTTPResponsePrinter"/>
<direct-parameter type="http response"
description="HTTP response to print"
/>
</command>
И мы также реализуем эту команду:
#import <Foundation/Foundation.h>
@interface HTTPResponsePrinter : NSScriptCommand
@end
@implementation HTTPResponsePrinter
- (id)performDefaultImplementation {
// We expect a dictionary parameter
id directParameter = [self directParameter];
if (![directParameter isKindOfClass:[NSDictionary class]]) return nil;
NSDictionary * dict = directParameter;
NSLog(@"Dictionary is %@", dict);
return nil;
}
@end
И мы проверяем это:
tell application "MyCoolApp"
set httpResp to http get "http://stackoverflow.com"
print http response httpResp
end tell
И вот что наше приложение регистрирует в консоли:
Dictionary is {
body = "<!DOCTYPE html>...";
method = GET;
replyCode = 200;
success = 1;
}
Так что, конечно, это работает в обе стороны.
Ну, теперь вы можете жаловаться, что на самом деле это не произвольно , в конце концов вам нужно определить, какие ключи (могут) существовать и какого типа они будут иметь, если они существуют. Вы правы. Однако, как правило, данные не настолько произвольны, я имею в виду, ведь код должен понимать их, и поэтому они должны, по крайней мере, следовать определенным правилам и шаблонам.
Если вы действительно не знаете, какие данные ожидать, например, как инструмент дампа, который просто конвертирует между двумя четко определенными форматами данных без какого-либо понимания самих данных, почему вы вообще передаете их как запись? Почему бы вам просто не преобразовать эту запись в легко разбираемое строковое значение (например, список свойств, JSON, XML, CSV), а затем передать ее в виде строки Какао и, наконец, преобразовать ее обратно в объекты? Это очень простой, но очень мощный подход. Синтаксический анализ списка свойств или JSON в Какао выполняется, возможно, с четырьмя строками кода. Хорошо, возможно, это не самый быстрый подход, но тот, кто упоминает AppleScript и высокую производительность в одном предложении, уже совершил фундаментальную ошибку; AppleScript, конечно, может быть много, но «быстрый» - это не то, что вы можете ожидать.