Objective c JXA:从CoreServices访问CFString常量

Objective c JXA:从CoreServices访问CFString常量,objective-c,core-services,javascript-automation,Objective C,Core Services,Javascript Automation,使用内置的ObjC桥,通过$对象自动公开基础框架中的枚举和常量;e、 g: $.NSUTF8StringEncoding // -> 4 但是,在较低级别的API中也有一些有用的、不会自动导入的CFString常量,即CoreServices中定义常用值的kUTType*常量,例如UTI“public.html” 虽然您可以使用ObjC.import('CoreServices')导入它们,但它们的字符串值(不容易)访问,可能是因为其类型是CFString[Ref]: ObjC.imp

使用内置的ObjC桥,通过
$
对象自动公开
基础
框架中的枚举和常量;e、 g:

$.NSUTF8StringEncoding  // -> 4
但是,在较低级别的API中也有一些有用的、不会自动导入的
CFString
常量,即
CoreServices
中定义常用值的
kUTType*
常量,例如UTI
“public.html”

虽然您可以使用
ObjC.import('CoreServices')
导入它们,但它们的字符串值(不容易)访问,可能是因为其类型是
CFString[Ref]

ObjC.import('CoreServices') // import kUTType* constants; ObjC.import('Cocoa') works too
$.kUTTypeHTML  // returns an [object Ref] instance - how do you get its string value?
我还没有找到一种方法来获取返回内容的核心字符串:
ObjC.unwrap($.kUTTypeHTML)
不起作用,
ObjC.unwrap($.kUTTypeHTML[0])
(或
.deepUnwrap()

我想知道:

  • 如果有一种原生的JXA方法可以做到这一点,我会错过
  • 否则,如果需要使用
    ObjC.bindFunction()
    CFString*()
    函数定义可以解决问题的绑定,例如to或,但我不清楚如何转换ObjC签名

$。KuttypeTHtml似乎返回CFDictionary(见下文),因此您应该在以下位置找到可用的方法:

编辑:事实证明,JXA ObjC CF交互中的某些类型复杂性意味着下面的代码片段不是学习CF对象引用类型的可靠或普遍适用的方法。(见下面的讨论)


虽然我不理解所有的含义,但以下似乎有效:

$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'

# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
kUTType*
常量被定义为
CFStringRef
,并且
CFStringGetCStringPtr
返回指定编码的
CFString
对象的内部C字符串(如果可以提取),否则返回
NULL

对于内置常量,似乎总是返回一个C字符串(而不是
NULL
),通过C数据类型映射到JXA数据类型,它可以直接在JavaScript中使用:

 $.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
有关背景信息(从OSX 10.11.1开始),请继续阅读


JXA本身无法识别
CFString
对象,即使它们可以“免费桥接”到
NSString
,JXA确实可以识别这种类型

您可以通过执行
$.NSString.stringWithString($.KuttypeThypHTML).js
,验证JXA不知道
CFString
NSString
的等价性,该命令应返回输入字符串的副本,但由于
-[\uu NSDictionaryM length]:无法识别的选择器发送到实例而失败

不识别
CFString
是我们的出发点:
$。KuttypeTHtml
的类型是
CFString[Ref]
,但JXA不返回它的JS字符串表示,只返回
[object Ref]

注意:以下部分是推测性的——如果我错了,一定要告诉我

不识别
CFString
还有另一个副作用,即调用接受泛型类型的
CF*()
函数(或接受JXA不知道的免费桥接
CF*
类型的Cocoa方法):
在这种情况下,如果参数类型与调用函数的参数类型不完全匹配,JXA显然会隐式地将输入对象包装在
CFDictionary
实例中,该实例的唯一条目具有key
type
,关联值包含原始对象。
[1]

这大概就是上面的
$.NSString.stringWithString()
调用失败的原因:它被传递给
CFDictionary
包装器,而不是
CFString
实例

另一个例子是
CFGetTypeID()
函数,它需要一个
CFTypeRef
参数:即任何
CF*
类型

由于JXA不知道传递
CFStringRef
参数作为
CFTypeRef
参数是可以的,因此它错误地执行了上述包装,实际上传递了一个
CFDictionary
实例:

$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)
这就是我在中国的经历

对于给定的
CF*
函数,您可以通过使用
ObjC.bindFunction()
重新定义感兴趣的函数来绕过默认行为:

// Redefine CFGetTypeID() to accept any type as-is:
ObjC.bindFunction('CFGetTypeID', ['unsigned long', [ 'void *']])
现在,
$.CFGetTypeID($.kUTTypeHTML)
正确返回
7
CFString

注意:重新定义的
$.CFGetTypeID()
返回一个JS
Number
实例,而原始的返回一个底层数字的字符串表示形式(
CFTypeID
值)

通常,如果您想非正式地了解给定
CF*
实例的特定类型,请使用
CFShow()
,例如:

$.CFShow($.kUTTypeHTML) // -> '{\n    type = "{__CFString=}";\n}'
注意:
CFShow()
不返回任何内容,而是直接打印到stderr,因此无法在JS中捕获输出。
您可以使用
ObjC.bindFunction('CFShow',['void',['void*']])重新定义
CFShow
,以便不显示包装器字典

对于本机识别的CF*类型(映射到JS原语的类型),您将直接看到特定的类型(例如,
CFBoolean
For
false
);对于未知的(因此是包装的)实例,您将看到如上所述的包装器结构-请继续阅读以了解更多信息


[1] 运行以下命令可以了解传递未知类型时JXA生成的包装器对象:

// Note: CFShow() prints a description of the type of its argument
//  directly to stderr.
$.CFShow($.kUTTypeHTML) // -> '{\n    type = "{__CFString=}";\n}'

// Alternative that *returns* the description as a JS string:
$.CFStringGetCStringPtr($.CFCopyDescription($.kUTTypeHTML), 0) // -> (see above)
类似地,使用
NSDictionary
CFDictionary
的已知到JXA的等价性

ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))
返回
{“type”:“{uuu CFString=}”
,即属性为
type
的JS对象,其值在ObjC桥调用往返之后,仅为原始
CFString
实例的字符串表示形式ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))
ObjC.bindFunction('CFMakeCollectable', [ 'id', [ 'void *' ] ]);

var cfString = $.CFStringCreateWithCString(0, "foo", 0); // => [object Ref]
var nsString = $.CFMakeCollectable(cfString);            // => $("foo")
Ref.prototype.toNS = function () { return $.CFMakeCollectable(this); }
ObjC.import('CoreServices')

$.kUTTypeHTML.toNS() // => $("public.html")