【问题标题】:How to pass an arbitrary AppleScript Record to Cocoa in a scriptable app?如何在可编写脚本的应用程序中将任意 AppleScript 记录传递给 Cocoa?
【发布时间】:2011-01-08 14:45:15
【问题描述】:

我有一个 Cocoa 应用程序,它带有一个在 .sdef XML 文件中描述的 AppleScript 字典。 sdef 中定义的所有 AppleScript 类、命令等都是工作属性。

除了我的“提交表单”命令。 “提交表单”命令是我唯一尝试将参数传递的命令,该参数是从 AppleScript 到 Cocoa 的任意信息哈希表。我认为这应该通过传递 AppleScript record 来完成,它将在 Cocoa 端自动转换为 NSDictionary

tell application "Fluidium"
    tell selected tab of browser window 1
        submit form with name "foo" with values {bar:"baz"}
    end tell
end tell

“with values”参数是我遇到问题的record -> NSDictionary 参数。请注意,不能预先知道/定义记录/字典的键。它们是任意的。

这是我的 sdef XML 中这个命令的定义:

<command name="submit form" code="FuSSSbmt" description="...">
    <direct-parameter type="specifier" optional="yes" description="..."/>
    <parameter type="text" name="with name" code="Name" optional="yes" description="...">
        <cocoa key="name"/>
    </parameter>
    <parameter type="record" name="with values" code="Vals" optional="yes" description="...">
        <cocoa key="values"/>
    </parameter>
</command>

我有一个“选项卡”对象,它在 sdef 中响应这个命令:

<class name="tab" code="fTab" description="A browser tab.">
    ...
    <responds-to command="submit form">
        <cocoa method="handleSubmitFormCommand:"/>
    </responds-to>

和可可:

- (id)handleSubmitFormCommand:(NSScriptCommand *)cmd {
    ...
}

“选项卡”对象正确响应我定义的所有其他 AppleScript 命令。如果我不发送可选的“带值”参数,“选项卡”对象也会响应“提交表单”命令。所以我知道我的基础设置正确。唯一的问题似乎是任意的record->NSDictionary 参数。

当我在AppleScript Editor.app 中执行上面的 AppleScript 时,我在 Cocoa 端收到此错误:

+[NSDictionary scriptingRecordWithDescriptor:]: unrecognized selector sent to class 0x7fff707c6048

AppleScript 方面的这个:

error "Fluidium got an error: selected tab of browser window 1 doesn’t understand the submit form message." number -1708 from selected tab of browser window 1

谁能告诉我我错过了什么?作为参考,整个应用程序在 GitHub 上是开源的:

http://github.com/itod/fluidium

【问题讨论】:

  • 我怀疑我可能没有链接到一些我需要链接到的框架...?

标签: macos cocoa applescript cocoa-scripting


【解决方案1】:

Cocoa 会将NSDictionary 对象无缝转换为 AppleScript (AS) 记录,反之亦然,您只需告诉它如何执行此操作。

首先,您需要在脚本定义 (.sdef) 文件中定义 record-type,例如

<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 键,则不需要&lt;cocoa&gt; 标签(上例中的successmethodbody),如果不是,您可以使用&lt;cocoa&gt; 标签告诉Cocoa读取此值的正确键(在上面的示例中,code 是 AS 记录中的名称,但在 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 脚本指南简介”中的Table 1-1

当然,一个值本身可以是另一个嵌套记录,只需为其定义一个record-type,在property 规范中使用record-type 名称,在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 当然可能很多,但“快速”并不是您可以期待的属性。

【讨论】:

  • 很全面的回答,不过没必要用信号量,NSScriptCommandsuspendresume方法。
  • @vadian suspendresume 如何让我的主线程等待异步脚本命令完成?据我了解文档,它用于暂停/恢复命令的执行,但这不是我使用信号量的目的。
  • 只需将dispatch_semaphore_create 替换为[self suspendExecution];,将dispatch_semaphore_signal(sem); 替换为[self resumeExecutionWithResult:result]; 并删除dispatch_semaphore_wait...。它与信号量完全相同,但以本机方式。也许你可以将resume 分派到主线程。
【解决方案2】:

正确 -- NSDictionaries 和 AppleScript 记录似乎可以混合使用,但实际上它们并没有(NSDictionaries 使用对象键——比如字符串)AppleScript 记录使用四个字母字符代码(这要归功于他们的 AppleEvent/Classic Mac OS 遗产)。

this thread on Apple's AppleScript Implementer's mailing list

因此,在您的情况下,您实际需要做的是解压缩您拥有的 AppleScript 记录并将其转换为您的 NSDictionary。您可以自己编写代码,但它很复杂并且深入到 AE 管理器中。

然而,这项工作实际上已经在appscript/appscript-objc 的一些底层代码中为您完成(appscript 是一个用于 Python、Ruby 和 Objective-C 的库,可让您与 AppleScriptable 应用程序进行通信,而无需实际使用 AppleScript。appscript- objc 可以在使用 Cocoa 脚本的地方使用,但该技术的限制较少。)

代码是available on sourceforge。几周前我向作者提交了一个补丁,这样您就可以构建 appscript-objc 的底层基础,这就是您在这种情况下所需要的:您需要做的就是打包和解压缩 Applescript/AppleEvent 记录。

对于其他 google 用户,还有另一种方法可以做到这一点,即不使用 appscript:ToxicAppleEvents。里面有一种方法可以将字典翻译成 Apple 事件记录。

【讨论】:

  • Cocoa 在 10.0 发布时就已经知道如何在 AS 记录和 NSDictionary 之间进行转换。但只有当您使用旧式 Script Suite 和 Script Terminology 文件(从 10.0 到 10.3 的标准)时,才会自动发生这种情况。如果您使用新的 XML 描述(.sdef,在 10.2 中引入,自 10.4 起成为标准),您将必须通过定义自定义记录类型来告诉 Cocoa 这种翻译是如何工作的。您的其余回复包含一些事实,有点虚假,并且缺少真正做到这一点的最简单方法。
【解决方案3】:

如果您知道要包装的字典中的字段以及要映射到/从 AppleScript 映射的键类型是可预测的,那么最好的解决方案似乎是 to use a record definition,正如另一个答案中所述,该答案也有助于链接到 Apple 的至少我个人完全错过了脚本指南的文档。

如果上述要求不符合您的需求,另一种解决方案是将+scriptingRecordWithDescriptor: 实现为 NSDictionary 的类别。我在提到的问题的Fluidium 项目中找到了这个解决方案。这是来自NSDictionary+FUScripting.m的粘贴:

@implementation NSDictionary (FUScripting)

+ (id)scriptingRecordWithDescriptor:(NSAppleEventDescriptor *)inDesc {
    //NSLog(@"inDesc: %@", inDesc);

    NSMutableDictionary *d = [NSMutableDictionary dictionary];

    NSAppleEventDescriptor *withValuesParam = [inDesc descriptorForKeyword:'usrf']; // 'usrf' keyASUserRecordFields
    //NSLog(@"withValuesParam: %@", withValuesParam);

    NSString *name = nil;
    NSString *value = nil;

    // this is 1-indexed!
    NSInteger i = 1;
    NSInteger count = [withValuesParam numberOfItems];
    for ( ; i <= count; i++) {
        NSAppleEventDescriptor *desc = [withValuesParam descriptorAtIndex:i];
        //NSLog(@"descriptorAtIndex: %@", desc);

        NSString *s = [desc stringValue];
        if (name) {
            value = s;
            [d setObject:value forKey:name];
            name = nil;
            value = nil;
        } else {
            name = s;
        }
    }

    return [d copy];
}

@end

我可以确认使用 +scriptingRecordWithDecriptor: 和等效类型的自定义命令对我有用。

【讨论】:

  • 大声笑。这是我在 Stack Overflow 上提出的这个原始问题的 (OP) 解决方案。但你是对的:)。
  • 托德:是的,我确实只是在你的问题和 Fluidium 源中挖掘了更多内容,并从返回值中删除了一个 -autorelease 到 ARCify 它 LOL... 在这里列出是为了方便其他人在我确认它“仍然”有效后找到。我发现很多与 AppleScript 相关的 Stack Overflow 问题没有得到解答,这些问题需要太多的侦探工作:-)
  • 哇,好复杂。所需要的只是在您的sdef 文件中定义一个自定义record-type,而Cocoa 将为您完成所有的魔法。查看我的回复。
  • 非常好。如果您没有在记录中包含键的类型定义,它们是否会包含在记录中?
  • type 是属性的必填字段,如果您未能指定类型,则整个 sdef 将被忽略。如果NSDictionary 或记录包含未在您的sdef 中定义的字段,则这些字段根本不会被转换(它们会被静默忽略)。如果数据真的是任意的,我根本不会使用字典,我会在 AS 中创建一个字符串并将其传递给 Cocoa 并解析它(例如,一个 JSON 字符串可以用 4 行代码解析 - 或者如果你必须支持非常旧的 MacOS 版本,我会使用 XML plist,还有 4 行代码来解析)。
【解决方案4】:

11.9.2016,Mac OS 10.11.6 问题是:如何将AppleScript记录转换成可可世界的NSDictionary?

AppleScript 记录使用 AppleScript 属性作为键,使用数字或字符串作为值。

NSDictionary 使用对应的 cocoa 键作为键(以 NSString 对象的形式)和 NSNumber 或 NSString 值用于 AppleScript 记录中四种最基本的类型:字符串、整数、双精度和布尔值。

+ (id)scriptingRecordWithDescriptor:(NSAppleEventDescriptor *)inDesc 的建议解决方案在我的情况下不起作用。

我的实现中的基本变化是 AppleScript 环境中的每个类都定义了自己的属性和 AppleScript 代码。要确定的关键对象是一个 NSScriptClassDescription,其中包含 AppleScript 代码和 Cocoa 键之间的关系。 另一个复杂情况是,在方法中用作参数的 NSAppleEventDescriptor 表示传入的 AppleScript 记录(或在我的情况下的记录列表)。这个 NSAppleEventDescriptor 可以有不同的形式。

AppleScript 记录中的一个条目是特殊的:{class:"script class name"}。代码测试它的存在。

您必须在代码中做的唯一替换是为 "Name of your apple script suite" 引入您的应用程序 AppleScript 套件的名称。 该方法实现为 NSDictionary 上的 Category

#import "NSDictionary+AppleScript.h"

@implementation NSDictionary (AppleScript)

// returns a Dictionary from a apple script record
+ (NSArray <NSDictionary *> * )scriptingRecordWithDescriptor:(NSAppleEventDescriptor *)anEventDescriptor {
    NSScriptSuiteRegistry * theRegistry = [NSScriptSuiteRegistry sharedScriptSuiteRegistry] ;

    DescType theScriptClassDescriptor = [anEventDescriptor descriptorType] ;

    DescType printDescriptorType = NSSwapInt(theScriptClassDescriptor) ;
    NSString * theEventDescriptorType = [[NSString alloc] initWithBytes:&printDescriptorType length:sizeof(DescType) encoding:NSUTF8StringEncoding] ;
    //NSLog(@"Event descriptor type: %@", theEventDescriptorType) ; // "list" if a list, "reco" if a simple record , class identifier if a class

    // Forming a list of AppleEventDescriptors
    NSInteger i ;
    NSAppleEventDescriptor * aDescriptor ;
    NSMutableArray <NSAppleEventDescriptor*> * listOfEventDescriptors = [NSMutableArray array] ;
    if ([theEventDescriptorType isEqualToString:@"list"]) {
        NSInteger numberOfEvents = [anEventDescriptor numberOfItems] ;
        for (i = 1 ; i <= numberOfEvents ; i++) {
            aDescriptor = [anEventDescriptor descriptorAtIndex:i] ;
            if (aDescriptor) [listOfEventDescriptors addObject:aDescriptor] ;
        }
    }
    else [listOfEventDescriptors addObject:anEventDescriptor] ;

    // transforming every NSAppleEventDescriptor into an NSDictionary - key: cocoa key - object: NSString - the parameter value as string
    NSMutableArray <NSDictionary *> * theResult = [NSMutableArray arrayWithCapacity:listOfEventDescriptors.count] ;
    for (aDescriptor in listOfEventDescriptors) {
        theScriptClassDescriptor = [aDescriptor descriptorType] ;

        DescType printDescriptorType = NSSwapInt(theScriptClassDescriptor) ;
        NSString * theEventDescriptorType = [[NSString alloc] initWithBytes:&printDescriptorType length:sizeof(DescType) encoding:NSUTF8StringEncoding] ;
        //NSLog(@"Event descriptor type: %@", theEventDescriptorType) ;

        NSMutableDictionary * aRecord = [NSMutableDictionary dictionary] ;
        NSInteger numberOfAppleEventItems = [aDescriptor numberOfItems] ;
        //NSLog(@"Number of items: %li", numberOfAppleEventItems) ;

        NSScriptClassDescription * (^determineClassDescription)() = ^NSScriptClassDescription *() {
            NSScriptClassDescription * theResult ;

            NSDictionary * theClassDescriptions = [theRegistry classDescriptionsInSuite:@"Arcadiate Suite"] ;
            NSArray * allClassDescriptions = theClassDescriptions.allValues ;
            NSInteger numOfClasses = allClassDescriptions.count ;
            if (numOfClasses == 0) return theResult ;

            NSMutableData * thePropertiesCounter = [NSMutableData dataWithLength:(numOfClasses * sizeof(NSInteger))] ;
            NSInteger *propertiesCounter = [thePropertiesCounter mutableBytes] ;
            AEKeyword aKeyWord  ;
            NSInteger classCounter = 0 ;
            NSScriptClassDescription * aClassDescription ;
            NSInteger i ;
            NSString * aCocoaKey ;
            for (aClassDescription in allClassDescriptions) {
                for (i = 1 ; i <= numberOfAppleEventItems ; i++) {
                    aKeyWord = [aDescriptor keywordForDescriptorAtIndex:i] ;
                    aCocoaKey = [aClassDescription keyWithAppleEventCode:aKeyWord] ;
                    if (aCocoaKey.length > 0) propertiesCounter[classCounter] ++ ;
                }
                classCounter ++ ;
            }
            NSInteger maxClassIndex = NSNotFound ;
            for (i = 0 ; i < numOfClasses ; i++) {
                if (propertiesCounter[i] > 0) {
                    if (maxClassIndex != NSNotFound) {
                        if (propertiesCounter[i] > propertiesCounter[maxClassIndex]) maxClassIndex = i ;
                    }
                    else maxClassIndex = i ;
                }
            }
            //NSLog(@"Max class index: %li", maxClassIndex) ;
            //if (maxClassIndex != NSNotFound) NSLog(@"Number of matching properties: %li", propertiesCounter[maxClassIndex]) ;
            if (maxClassIndex != NSNotFound) theResult = allClassDescriptions[maxClassIndex] ;
            return theResult ;
        } ;

        NSScriptClassDescription * theRelevantScriptClass ;
        if ([theEventDescriptorType isEqualToString:@"reco"]) theRelevantScriptClass = determineClassDescription() ;
        else theRelevantScriptClass = [theRegistry classDescriptionWithAppleEventCode:theScriptClassDescriptor] ;
        if (theRelevantScriptClass) {
        //NSLog(@"Targeted Script Class: %@", theRelevantScriptClass) ;

            NSString * aCocoaKey, *stringValue ;
            NSInteger integerValue ;
            BOOL booleanValue ;
            id aValue ;
            stringValue = [theRelevantScriptClass implementationClassName] ;
            if (stringValue.length > 0) aRecord[@"className"] = aValue ;
            AEKeyword aKeyWord ;
            NSAppleEventDescriptor * parameterDescriptor ;
            NSString * printableParameterDescriptorType ;
            DescType parameterDescriptorType ;
            for (i = 1 ; i <= numberOfAppleEventItems ; i++) {
                aValue = nil ;
                aKeyWord = [aDescriptor keywordForDescriptorAtIndex:i] ;
                aCocoaKey = [theRelevantScriptClass keyWithAppleEventCode:aKeyWord] ;
                parameterDescriptor = [aDescriptor paramDescriptorForKeyword:aKeyWord] ;
                parameterDescriptorType = [parameterDescriptor descriptorType] ;
                printDescriptorType = NSSwapInt(parameterDescriptorType) ;
                printableParameterDescriptorType = [[NSString alloc] initWithBytes:&printDescriptorType length:sizeof(DescType) encoding:NSUTF8StringEncoding] ;
                //NSLog(@"Parameter type: %@", printableParameterDescriptorType) ;

                if ([printableParameterDescriptorType isEqualToString:@"doub"]) {
                    stringValue = [parameterDescriptor stringValue] ;
                    if (stringValue.length > 0) {
                        aValue = @([stringValue doubleValue]) ;
                    }
                }
                else if ([printableParameterDescriptorType isEqualToString:@"long"]) {
                    integerValue = [parameterDescriptor int32Value] ;
                    aValue = @(integerValue) ;
                }
                else if ([printableParameterDescriptorType isEqualToString:@"utxt"]) {
                    stringValue = [parameterDescriptor stringValue] ;
                    if (stringValue.length > 0) {
                        aValue = stringValue ;
                    }
                }
                else if ( ([printableParameterDescriptorType isEqualToString:@"true"]) || ([printableParameterDescriptorType isEqualToString:@"fals"]) ) {
                    booleanValue = [parameterDescriptor booleanValue] ;
                    aValue = @(booleanValue) ;
                }
                else {
                    stringValue = [parameterDescriptor stringValue] ;
                    if (stringValue.length > 0) {
                        aValue = stringValue ;
                    }
                }
                if ((aCocoaKey.length != 0) && (aValue)) aRecord[aCocoaKey] = aValue ;
            }
        }
        [theResult addObject:aRecord] ;
    }
    return theResult ;
}
@end

【讨论】:

  • 这个问题已经有一个公认的答案,以及其他几个非常详细的答案。您的确实提供了任何其他信息。
  • 是的,我的解决方案添加了新信息,是的,对于简单的情况,另一个答案就足够了。最简单的解决方案是在 AppleScript 中定义一个新类型——类似于真正的记录类型,它定义了将在 AppleScript 中的记录中使用的所有键。每个要使用的记录都应该被定义为来自这个真正的记录类型。 - 但是对于较大的 Apple 套装,这往往会失控。根据类的不同,相同的键在 Cocoa 中可能有不同的实现 - 因此需要更真实的 - 类类型 - 解决方案。这是我的代码提供的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-08
  • 2019-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-18
相关资源
最近更新 更多