Cocoa 会无缝地将NSDictionary
对象转换为 AppleScript (AS) 记录,反之亦然,您只需告诉它如何做到这一点。
首先,您需要record-type
在脚本定义 ( .sdef
) 文件中定义 a,例如
<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
如果 name 与key相等,<cocoa>
则不需要标签(success
上例中为method
, body
),如果不是,则可以使用<cocoa>
标签告诉 Cocoa 读取此值的正确键(上例code
中为 AS 中的 name记录,但在NSDictionary
密钥中将replyCode
改为;我只是为了演示目的而制作的)。
告诉 Cocoa 该字段应具有什么 AS 类型非常重要,否则 Cocoa 不知道如何将该值转换为 AS 值。默认情况下,所有值都是可选的,但如果它们存在,它们必须具有预期的类型。这是最常见的 Foundation 类型如何与 AS 类型匹配的小表格(不完整):
AS Type | Foundation Type
-------------+-----------------
boolean | NSNumber
date | NSDate
file | NSURL
integer | NSNumber
number | NSNumber
real | NSNumber
text | NSString
参见Apple 的“ Cocoa 脚本简介指南”的表 1-1
当然,一个值本身可以是另一个嵌套记录,只需record-type
为它定义一个,使用规范中的record-type
名称,然后值必须是匹配的字典。property
NSDictionary
好吧,让我们尝试一个完整的样本。让我们在.sdef
文件中定义一个简单的 HTTP get 命令:
<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),然后将其作为字符串传递给 Cocoa,最后将其转换回对象?这是一个非常简单但非常强大的方法。在 Cocoa 中解析属性列表或 JSON 可能只需要四行代码。好吧,这可能不是最快的方法,但是在一个句子中提到 AppleScript 和高性能的人一开始就犯了一个根本性的错误;AppleScript 当然可能很多,但“快速”并不是您可以期望的属性。