Cocoaは、オブジェクトをAppleScript(AS)レコードにシームレスに変換NSDictionary
します。その逆の場合は、その方法を指示するだけで済みます。
まず、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>
タグは必要ありません。そうでない場合は、タグを使用して、この値を読み取るための正しいキーをCocoaに伝えることができます(上記の例では、AS内の名前です)。記録しますが、キーには代わりになります。ここでは、デモンストレーションの目的でこれを作成しました)。success
method
body
<cocoa>
code
NSDictionary
replyCode
このフィールドが持つASタイプをCocoaに伝えることは非常に重要です。そうしないと、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
ファイルで簡単なHTTPgetコマンドを定義しましょう。
<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;
}
したがって、もちろん、それは両方の方法で機能します。
さて、これは実際には恣意的ではないことに不満を言うかもしれません。結局のところ、どのキーが存在するか(存在する可能性があります)、存在する場合はどのタイプになるかを定義する必要があります。あなたが正しいです。ただし、通常、データはそれほど恣意的ではありません。つまり、すべてのコードがデータを理解できる必要があるため、少なくとも特定の種類のルールとパターンに従う必要があります。
データ自体を理解せずに2つの明確に定義されたデータ形式間で変換するダンプツールのように、どのデータを期待するのか本当にわからない場合、なぜそれをレコードとして渡すのですか?そのレコードを簡単に解析可能な文字列値(プロパティリスト、JSON、XML、CSVなど)に変換してから、Cocoaを文字列として渡し、最後にオブジェクトに変換して戻しませんか?これは非常にシンプルですが、非常に強力なアプローチです。CocoaでのプロパティリストまたはJSONの解析は、おそらく4行のコードで実行されます。さて、これは最速のアプローチではないかもしれませんが、AppleScriptと高性能について一文で言及している人は、そもそも根本的な間違いを犯しています。AppleScriptは確かにたくさんあるかもしれませんが、「高速」はあなたが期待できる特性のどれでもありません。