没有 API 可以将 CFString 直接写入任何文件(包括 stdout 或 stderr),因为您只能将字节写入文件。字符是一个(有点)更理想的概念;它们太高级而无法写入文件。这就像说“我想写这些像素”;您必须首先决定将它们写入的格式(例如 PNG),然后以该格式对其进行编码,然后写入该数据。
对字符也是如此。您必须将它们编码为某种格式的字节,然后写入这些字节。
将字符编码为字节/数据
首先,您必须选择一种编码。要在终端上显示,您可能需要 UTF-8,即kCFStringEncodingUTF8
. 对于写入文件……您通常需要 UTF-8。事实上,除非您特别需要其他东西,否则您几乎总是需要 UTF-8。
接下来,您必须将字符编码为字节。创建 C 字符串是一种方法;另一种是创建一个CFData对象;还有一个是直接提取字节(非空终止)。
你说你想坚持使用 CF,所以我们将跳过 C 字符串选项(无论如何效率较低,因为任何调用write
都必须调用strlen
)——它更容易,但速度更慢,特别是当你在大字符串上使用它时和/或经常。相反,我们将创建 CFData。
幸运的是,CFString 提供了一个 API 来从 CFString 的内容创建一个 CFData 对象。不幸的是,这只适用于创建外部表示。您可能不想将其写入标准输出;它只适合写为常规文件的全部内容。
所以,我们需要下拉一个级别并自己获取字节。此函数采用缓冲区(内存区域)和该缓冲区的大小(以字节为单位)。
不要用于CFStringGetLength
缓冲区的大小。这计算字符数,而不是字节数,并且字符数和字节数之间的关系并不总是线性的。(例如,某些字符可以在单个字节中以 UTF-8 编码……但不是全部。不是几乎全部。对于其他字符,所需的字节数各不相同。)
正确的方法是调用CFStringGetBytes
两次:一次没有缓冲区(NULL
),然后它会简单地告诉你它会给你多少字节(而不是试图写入你没有给它的缓冲区);然后,您创建一个该大小的缓冲区,然后使用该缓冲区再次调用它。
您可以使用 创建一个缓冲区malloc
,但您想坚持使用 CF 的东西,所以我们将这样做:创建一个 CFMutableData 对象,其容量是您从第一次CFStringGetBytes
调用中获得的字节数,将其长度增加到相同字节数,然后获取数据的可变字节指针。该指针是指向您需要写入的缓冲区的指针;它是您传递给第二次调用的指针CFStringGetBytes
。
回顾一下到目前为止的步骤:
- 在没有缓冲区
CFStringGetBytes
的情况下调用以了解缓冲区需要多大。
- 创建该容量的 CFMutableData 对象并将其长度增加到该大小。
- 获取 CFMutableData 对象的可变字节指针,即您的缓冲区,并
CFStringGetBytes
再次调用,这次使用缓冲区,将字符编码为数据对象中的字节。
写出来
要将字节/数据写入纯 CF 中的文件,您必须使用CFWriteStream。
可悲的是,没有 CF 等价于漂亮的 Cocoa API,例如[NSFileHandle fileHandleWithStandardOutput]
. 创建到 stdout 的写入流的唯一方法是使用包含在 URL 中的 stdout 路径来创建它。
您可以从路径轻松创建 URL;标准输出设备的路径是/dev/stdout
,因此创建 URL 如下所示:
CFURLRef stdoutURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("/dev/stdout"), kCFURLPOSIXPathStyle, /*isDirectory*/ false);
(当然,就像您创建的所有内容一样,您需要发布它。)
有了 URL,您就可以为所引用的文件创建一个写入流。然后,您必须打开流,然后您可以将数据写入其中(您需要获取数据的字节指针及其长度),最后关闭流。
请注意,如果您写出的内容不以换行符结尾,您可能会丢失/未显示文本。NSLog
当它代表您写入 stderr 时为您添加一个换行符;当您自己写信给 stderr 时,您必须这样做(或承担后果)。
所以: