3

JXA及其内置的 ObjC 桥,通过对象Foundation自动从框架中公开枚举和常量;$例如:

$.NSUTF8StringEncoding  // -> 4

但是,CFString在较低级别的 API 中也有一些有用的常量不会自动导入,即定义常用UTIkUTType*值的常量,例如UTI 。CoreServiceskUTTypeHTML"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*(),例如 toCFStringGetCString()CFStringGetCStringPtr(),但对我来说如何翻译 ObjC 签名并不明显。
4

3 回答 3

3

虽然我不了解所有含义,但以下似乎可行:

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

# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto

kUTType*常量定义为CFStringRef,并以指定的编码CFStringGetCStringPtr返回CFString对象的内部 C 字符串,前提它可以“在恒定时间内没有内存分配和复制”NULL其他方式提取。

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

 $.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true

有关背景信息(从 OSX 10.11.1 开始),请继续阅读。


JXA 本身并不识别CFString对象,即使它们可以“免费桥接”到NSStringJXA确实识别的类型。

您可以通过执行来验证 JXA 不知道CFString和的等价性,这应该返回输入字符串的副本,但会失败并返回。NSString$.NSString.stringWithString($.kUTTypeHTML).js-[__NSDictionaryM length]: unrecognized selector sent to instance

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

注意:以下内容部分是推测性的 - 如果我错了,请告诉我。

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

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

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

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

$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)

这是houthakker他的解决方案尝试中所经历的。

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

// Redefine CFGetTypeID() to accept any type as-is:
ObjC.bindFunction('CFGetTypeID', ['unsigned long', [ 'void *']])

现在,$.CFGetTypeID($.kUTTypeHTML)正确返回7( CFString)。

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

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

$.CFShow($.kUTTypeHTML) // -> '{\n    type = "{__CFString=}";\n}'

注意:CFShow()不返回任何内容,而是直接打印到stderr,因此您无法在 JS 中捕获输出。
您可以重新定义CFShowwithObjC.bindFunction('CFShow', ['void', [ 'void *' ]])以免显示包装器字典。

对于本机识别的 CF* 类型——那些映射到 JS 原语的类型——你会直接看到特定的类型(例如,CFBooleanfor 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)

类似地,使用已知的 JXA 等价NSDictionaryCFDictionary

ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))

返回{"type":"{__CFString=}"},即,具有属性的 JS 对象,type其值在这一点上 - 在 ObjC 桥调用往返之后- 仅仅是可能是原始实例的字符串表示。CFString


houthakker 的解决方案尝试还包含一个方便的代码片段,用于将实例的类型名称作为CF*字符串获取。

如果我们将它重构为一个函数并应用必要的重新定义CFGetTypeID(),我们会得到以下结果:

  • 需要一个hack来使它返回一个可预测的值(见注释和源代码)
  • 即使这样,随机字符有时也会作为返回字符串的结尾出现,例如CFString,而不是CFString.

如果有人解释为什么需要破解以及随机字符来自哪里,请告诉我。这些问题可能与内存管理相关,因为两者都CFCopyTypeIDDescription()返回CFStringCreateExternalRepresentation()调用者必须释放的对象,不知道 JXA 是否/如何/何时这样做。

/* 
  Returns the type name of the specified CF* (CoreFoundation) type instance.
  CAVEAT:
   * A HACK IS EMPLOYED to ensure that a value is consistently returned f
     those CF* types that correspond to JS primitives, such as CFNumber, 
     CFBoolean, and CFString:
     THE CODE IS CALLED IN A TIGHT LOOP UNTIL A STRING IS RETURNED.
     THIS SEEMS TO WORK WELL IN PRACTICE, BUT CAVEAT EMPTOR.
     Also, ON OCCASION A RANDOM CHARACTER APPEARS AT THE END OF THE STRING.
   * Only pass in true CF* instances, as obtained from CF*() function
     calls or constants such as $.kUTTypeHTML. Any other type will CRASH the
     function. 

  Example:
    getCFTypeName($.kUTTypeHTML) // -> 'CFString'  
*/
function getCFTypeName(cfObj) {

  // Redefine CFGetTypeID() so that it accepts unkown types as-is
  // Caution:
  //  * ObjC.bindFunction() always takes effect *globally*.
  //  * Be sure to pass only true CF* instances from then on, otherwise
  //    the function will crash.
  ObjC.bindFunction('CFGetTypeID', [ 'unsigned long', [ 'void *' ]])

  // Note: Ideally, we'd redefine CFCopyDescription() analogously and pass 
  // the object *directly* to get a description, but this is not an option:
  //   ObjC.bindFunction('CFCopyDescription', ['void *', [ 'void *' ]])
  // doesn't work, because, since we're limited to *C* types,  we can't describe
  // the *return* type in a way that CFStringGetCStringPtr() - which expects
  // a CFStringRef - would then recognize ('Ref has incompatible type').

  // Thus, we must first get a type's numerical ID with CFGetTypeID() and then
  // get that *type*'s description with CFCopyTypeIDDescription().
  // Unfortunately, passing the resulting CFString to $.CFStringGetCStringPtr()
  // does NOT work: it yields NULL - no idea why.
  // 
  // Using $.CFStringCreateExternalRepresentation(), which yields a CFData
  // instance, from which a C string pointer can be extracted from with 
  // CFDataGetBytePtr(), works:
  //  - reliably with non-primitive types such as CFDictionary
  //  - only INTERMITTENTLY with the equivalent types of JS primitive types
  //    (such as CFBoolean, CFString, and CFNumber) - why??
  //    Frequently, and unpredictably, `undefined` is returned.
  // !! THUS, THE FOLLOWING HACK IS EMPLOYED: THE CODE IS CALLED IN A TIGHT
  // !! LOOP UNTIL A STRING IS RETURNED. THIS SEEMS TO WORK WELL IN PRACTICE,
  // !! BUT CAVEAT EMPTOR.
  //    Also, sometimes, WHEN A STRING IS RETURNED, IT MAY CONTAIN A RANDOM
  //    EXTRA CHAR. AT THE END.
  do {
    var data = $.CFStringCreateExternalRepresentation(
            null, // use default allocator
            $.CFCopyTypeIDDescription($.CFGetTypeID(cfObj)), 
            0x08000100, // kCFStringEncodingUTF8
            0 // loss byte: n/a here
        ); // returns a CFData instance
    s = $.CFDataGetBytePtr(data)
  } while (s === undefined)
  return s
}
于 2015-12-02T04:02:04.430 回答
2

您可以通过首先重新绑定 CFMakeCollectable 函数使其接受 'void *' 并返回 'id' 来将 CF 类型强制为 NS 类型,然后使用该函数执行强制:

ObjC.bindFunction('CFMakeCollectable', [ 'id', [ 'void *' ] ]);

var cfString = $.CFStringCreateWithCString(0, "foo", 0); // => [object Ref]
var nsString = $.CFMakeCollectable(cfString);            // => $("foo")

为了使这更容易在您的代码中使用,您可以在 Ref 原型上定义一个 .toNS() 函数:

Ref.prototype.toNS = function () { return $.CFMakeCollectable(this); }

下面是如何使用这个带有 CFString 常量的新函数:

ObjC.import('CoreServices')

$.kUTTypeHTML.toNS() // => $("public.html")
于 2016-01-26T04:33:38.253 回答
1

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

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

https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFDictionaryRef/

ObjC.import('CoreServices')

var data = $.CFStringCreateExternalRepresentation(
        null, 
        $.CFCopyTypeIDDescription(
            $.CFGetTypeID($.kUTTypeHTML)
        ), 
        'UTF-8',
        0
    ); // CFDataRef


cPtr = $.CFDataGetBytePtr(data);

// --> "CFDictionary"
于 2015-12-01T15:21:16.607 回答