Cocoa 脚本:使用原始数据等特殊字符串类型

Cocoa Scripting: Use special string types like raw data

我的应用程序有一些我希望能够提供给 AppleScript 的原始数据内容,以便至少可以查看它,如果甚至不通过将其保存到文件或将其设置为支持的其他对象来处理的话它。

现在,我不明白使用哪种数据类型来实现这一点。

查看脚本编辑器的输出,例如:

tell application "Script Editor"
  the clipboard as record
    --> {Unicode text:"text",
         «class BBLM»:«data BBLM6C6C756E»,
         string:"text"}
end tell

如何 return 这些“数据 ...”,它们显然是实际数据的 4 字符代码和十六进制字符串编码字节的组合。

我已经尝试 returning 一个 NSData 对象,其中包含来自我的脚本 属性 的原始字节数据,但这不起作用。

更新

它似乎与实施 scripting<type>Descriptorscripting<type>WithDescriptor 有关。除了在 Sketch 示例代码中使用它之外,我找不到任何关于它的文档。我假设如果我碰巧在我的 Sdef 中定义了这样的自定义类型,这些将被调用。

但是:我不会提前知道我要发送的类型,所以我不能在Sdef中预先定义它们。我的情况更类似于 the clipboard:我有类似剪贴板的数据,我想 return,所以我在运行时只知道它们的 4 字符类型。这意味着不会通过这些处理程序询问我。必须有一些其他的方式来通用地创建和接收这些类型,就像剪贴板实现的方式一样。

神奇之处在于使用NSAppleEventDescriptor。它提供了很多初始化器。它是最终保存传递回调用 AppleScript(或 JXA 或任何使用脚本引擎的任何东西)的任何值的那个。

显然,任何 returned 到 Cocoa 脚本层的值,例如作为 NSString 的字符串和作为 NSNumber 的数值,最终都会被分配给 NSAppleEventDescriptor 对象,并通过该步骤进行转换为 AppleEvent 内部格式。

所以,如果我想 return 一串字节,例如存储在一个 NSData 对象中,我所要做的就是从我的 属性 方法:

-(id)returnSomeBytes {
    return [NSAppleEventDescriptor descriptorWithDescriptorType:'Raw ', myNSDataObject];
}

这将在 AppleScript 中作为 «data Raw ...»

我现在也明白为什么脚本引擎不会自动为我转换 NSData:它需要一个 NSData 不继承的类型代码。

反之亦然 - 任何此类原始数据都会作为 NSAppleEventDescriptor 传递到我的代码,然后我可以对其进行相应的解码。

RE:“...实现脚本Descriptor 和脚本WithDescriptor。我找不到关于此的任何文档...”

首先从 Cocoa 脚本指南 (2008) 中的 "Key-Value Coding and Cocoa Scripting" 部分开始。有大量这些方法将类型嵌入到方法名称中。许多也记录在基金会的 NSScriptKeyValueCoding 协议参考页面中,但您必须阅读 "Discussion" 部分才能找到它们。例如,在:

- (id)valueWithUniqueID:(id)uniqueID inPropertyWithKey:(NSString *)key

讨论说:"The method valueIn<Key>WithUniqueID: is invoked if it exists."

所以在小部件 class 中,您将实现 valueInWidgetsWithUniqueID:

scriptingDescriptor 和 scriptingWithDescriptor 是特殊的转换处理程序,在您使用应用程序的 .sdef 中的元素时使用,这就是它们出现在 Sketch 中以处理 typeRGBColor 数据类型的原因,a 3个整数的列表。我也无法在 Sketch 代码之外找到这些记录,但我可以确认

scriptingRGBColorDescriptor

由以下方法调用:

NSObject(NSScriptAppleEventConversion)
NSAppleEventDescriptor(NSScriptConversion)

回复:"However: I will not know the types I want to send in advance, so I cannot pre-define them in the Sdef."

有一种方法可以解决该问题:您可以 return 一个称为用户字段记录(typeUserField)的特殊列表结构。此记录包括交替的键和值描述符,并且不需要在 SDEF 中定义任何内容。

这是我去年在 ASOC 邮件列表上发布的项目: http://lists.apple.com/archives/applescriptobjc-dev/2015/Jan/msg00036.html

这是从 NSDictionary 构建 typeUserField 记录的代码(使用 AppleScript-ObjectiveC 代码)。

# ASOC implementation of - (NSAppleEventDescriptor *)scriptingRecordDescriptor for an NSDictionary
# Creates an empty record descriptor and an empty list descriptor, then
# Iterates over the dictionary and inserts descriptors for each key and each value into the list descriptor
# Finally, populates the record descriptor with the type 'usrf' and the list descriptor

on makeUserRecordDescriptor(aDict)

log aDict

set recordDescriptor to aedClass's recordDescriptor()
set listDescriptor   to aedClass's listDescriptor()

set typeUserField to 1970500198 -- 'usrf'

set itemIndex to 1 -- AS records are 1-based

repeat with aKey in aDict's allKeys()

    set aVal to aDict's valueForKey_(aKey)

    -- The values can be several different types. This code DOES NOT handle them all.

    set isStringValue  to aVal's isKindOfClass_(nssClass's |class|) = 1
    set isNumericValue to aVal's isKindOfClass_(nsnClass's |class|) = 1
    set isBooleanValue to aVal's className()'s containsString_("Boolean") = 1

    -- Insert a descriptor for the key into the list descriptor

    set anItem to aedClass's descriptorWithString_(aKey)
    listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
    set itemIndex to itemIndex + 1

    -- Insert a descriptor (of the correct type for the value) into the list descriptor

    if isStringValue
        set anItem to aedClass's descriptorWithString_(aVal)
    else if isBooleanValue
        set anItem to aedClass's descriptorWithBoolean_(aVal's boolValue())
    else if isNumericValue
        set intValue to aVal's intValue()
        set fpValue to aVal's doubleValue()

        if intValue = fpValue
            set anItem to  aedClass's descriptorWithInt32_(aVal's intValue())
        else
            set anItem to  aedClass's descriptorWithString_(aVal's stringValue) # TODO: 'doub'
        end
    else
        set anItem to  aedClass's descriptorWithString_("Unhandled Data Type")
    end

    listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
    set itemIndex to itemIndex + 1

end

recordDescriptor's setDescriptor_forKeyword_(listDescriptor, typeUserField)

return recordDescriptor

结束