假设我是用objc / swift编写的服务器。客户端正在向我发送大量数据,这实际上是一个很大的utf8编码字符串。作为服务器,我有我的NSInputStream触发事件,说它有数据要读取。我抓住数据并用它构建一个字符串。
然而,如果我获得的下一块数据落在utf8数据中的不幸位置怎么办?喜欢组成的角色。如果你试图将一块不合规的utf8附加到它上面,似乎会弄乱字符串。
有什么方法可以解决这个问题?我当时认为我可以将数据保存为NSData,但是我无论如何都不知道数据何时被接收(想想数据长度在标题中的HTTP)。
感谢您的任何想法。
答案 0 :(得分:6)
您可能希望在此处使用的工具是UTF8
。它将为您处理所有州的问题。有关您可能适应的简单示例,请参阅How to cast decrypted UInt8 to String?。
从UTF-8数据构建字符串的主要问题不是组成字符,而是多字节字符。 "拉丁文小写字母A" +"结合GRAVE ACCENT"即使分别解码每个字符也能正常工作。什么不起作用是收集你的第一个字节,解码它,然后附加解码的第二个字节。但UTF8
类型将为您处理此问题。您所需要做的就是将NSInputStream
与GeneratorType
联系起来。
这是我所谈论的基本(不完全生产就绪)的例子。首先,我们需要一种方法将NSInputStream
转换为生成器。这可能是最难的部分:
final class StreamGenerator {
static let bufferSize = 1024
let stream: NSInputStream
var buffer = [UInt8](count: StreamGenerator.bufferSize, repeatedValue: 0)
var buffGen = IndexingGenerator<ArraySlice<UInt8>>([])
init(stream: NSInputStream) {
self.stream = stream
stream.open()
}
}
extension StreamGenerator: GeneratorType {
func next() -> UInt8? {
// Check the stream status
switch stream.streamStatus {
case .NotOpen:
assertionFailure("Cannot read unopened stream")
return nil
case .Writing:
preconditionFailure("Impossible status")
case .AtEnd, .Closed, .Error:
return nil // FIXME: May want a closure to post errors
case .Opening, .Open, .Reading:
break
}
// First see if we can feed from our buffer
if let result = buffGen.next() {
return result
}
// Our buffer is empty. Block until there is at least one byte available
let count = stream.read(&buffer, maxLength: buffer.capacity)
if count <= 0 { // FIXME: Probably want a closure or something to handle error cases
stream.close()
return nil
}
buffGen = buffer.prefix(count).generate()
return buffGen.next()
}
}
对next()
的调用可以在此处阻止,因此不应在主队列上调用它,但除此之外,它是一个标准的生成器,它会吐出字节。 (这也是可能有许多我不会处理的小角落的部分,所以你想仔细考虑这一点。但是,它并不复杂。)
有了这个,创建一个UTF-8解码生成器几乎是微不足道的:
final class UnicodeScalarGenerator<ByteGenerator: GeneratorType where ByteGenerator.Element == UInt8> {
var byteGenerator: ByteGenerator
var utf8 = UTF8()
init(byteGenerator: ByteGenerator) {
self.byteGenerator = byteGenerator
}
}
extension UnicodeScalarGenerator: GeneratorType {
func next() -> UnicodeScalar? {
switch utf8.decode(&byteGenerator) {
case .Result(let scalar): return scalar
case .EmptyInput: return nil
case .Error: return nil // FIXME: Probably want a closure or something to handle error cases
}
}
}
您当然可以将其简单地转换为CharacterGenerator(使用Character(_:UnicodeScalar)
)。
最后一个问题是如果你想要组合所有组合标记,那么&#34;拉丁文小写字母A&#34;其次是&#34; COMBINING GRAVE ACCENT&#34;将永远一起返回(而不是作为他们的两个字符)。这实际上比听起来有点棘手。首先,您需要生成字符串,而不是字符。然后你需要一个很好的方法来了解所有组合字符是什么。这当然是可以知道的,但我在获得一个简单的算法时遇到了一些麻烦。没有&#34; combineMarkCharacterSet&#34;在可可。我还在考虑它。得到的东西&#34;主要是工作&#34;很简单,但我还不确定如何构建它以使其对所有Unicode都正确。
这里有一个小样本程序可以试用:
let textPath = NSBundle.mainBundle().pathForResource("text.txt", ofType: nil)!
let inputStream = NSInputStream(fileAtPath: textPath)!
inputStream.open()
dispatch_async(dispatch_get_global_queue(0, 0)) {
let streamGen = StreamGenerator(stream: inputStream)
let unicodeGen = UnicodeScalarGenerator(byteGenerator: streamGen)
var string = ""
for c in GeneratorSequence(unicodeGen) {
print(c)
string += String(c)
}
print(string)
}
还有一个小文:
Here is some normalish álfa你好 text And some Zalgo i̝̲̲̗̹̼n͕͓̘v͇̠͈͕̻̹̫͡o̷͚͍̙͖ke̛̘̜̘͓̖̱̬ composed stuff And one more line with no newline
(第二行是Zalgo encoded text,非常适合测试。)
我还没有在真正的阻止情况下对此进行任何测试,例如从网络上读取,但它应该根据NSInputStream
的工作原理(即它应该阻止直到那里)至少要读取一个字节,但是应该只用可用的任何东西填充缓冲区。
我已完成所有这些匹配GeneratorType
,以便轻松插入其他内容,但如果您没有使用GeneratorType
而是创建了您的错误处理可能会更好相反,使用next() throws -> Self.Element
自己的协议。抛出可以更容易地将错误传播到堆栈中,但会使插入for...in
循环变得更加困难。