虽然我不了解所有含义,但以下方法似乎有效:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) // -> 'public.html'
# Alternative, with explicit UTF-8 encoding specification
$.CFStringGetCStringPtr($.kUTTypeHTML, $.kCFStringEncodingUTF8) // ditto
kUTType* 常量定义为CFStringRef,CFStringGetCStringPtr 以指定编码返回一个CFString 对象的内部C 字符串,如果 可以提取"with no memory allocations and no copying, in constant time" - 或NULL 否则。
使用内置常量,似乎总是返回一个 C 字符串(而不是 NULL),由于 C 数据类型映射到 JXA 数据类型,它可以直接在 JavaScript 中使用:
$.CFStringGetCStringPtr($.kUTTypeHTML, 0) === 'public.html' // true
有关背景信息(从 OSX 10.11.1 开始),请继续阅读。
JXA 本身不识别 CFString 对象,即使它们可以“免费桥接”到 NSString,JXA 确实识别的类型。
您可以通过执行$.NSString.stringWithString($.kUTTypeHTML).js 来验证JXA 是否不知道CFString 和NSString 的等价性,这应该返回输入字符串的副本,而是以-[__NSDictionaryM length]: unrecognized selector sent to instance 失败。
不认识CFString 是我们的起点:$.kUTTypeHTML 是CFString[Ref] 类型,但JXA 不返回它的JS 字符串表示,只返回[object Ref]。
注意:以下部分是推测性的 - 如果我错了,请告诉我。
无法识别CFString 有另一个副作用,即在调用接受通用 类型的CF*() 函数(或接受JXA 是免费桥接CF* 类型的Cocoa 方法时)不知道):
在这种情况下,如果参数类型与调用函数的参数类型不完全匹配,JXA 显然会在 CFDictionary 实例中隐式包装输入对象,其唯一条目具有键 @987654347 @,其关联值包含原始对象。[1]
大概这就是上述$.NSString.stringWithString() 调用失败的原因:它被传递给CFDictionary 包装器而不是CFString 实例。
另一个例子是 CFGetTypeID() 函数,它需要一个 CFTypeRef 参数:即,any CF* 类型。
由于 JXA 不知道将 CFStringRef 参数原样作为 CFTypeRef 参数传递是可以的,因此它错误地执行了上述包装,而是有效地传递了 CFDictionary 实例:
$.CFGetTypeID($.kUTTypeHTML) // -> !! 18 (CFDictionary), NOT 7 (CFString)
这就是houthakker 在his solution attempt 所经历的。
对于给定的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 对应于 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 等效项 NSDictionary 和 CFDictionary,
ObjC.deepUnwrap($.NSDictionary.dictionaryWithDictionary( $.kUTTypeHTML ))
返回{"type":"{__CFString=}"},即具有type 属性的JS 对象,其值在此时 - 在ObjC-bridge 调用之后往返 - 仅仅是字符串可能是原始 CFString 实例的表示。
houthakker's solution attempt 还包含一个方便的 sn-p 代码,用于获取 CF* 实例的类型 name 作为字符串。
如果我们将它重构为一个函数并应用CFGetTypeID() 的必要重新定义,我们会得到以下结果:
- 需要一个 hack 使其返回一个可预测的值(参见 cmets 和源代码)
- 即便如此,随机字符有时也会作为返回字符串的结尾出现,例如
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
}