将stream(utf8)数据转换为string的安全方法是什么?
假设我是一个用objc / swift编写的服务器。 客户端发送了大量的数据,这实际上是一个很大的utf8编码的string。 作为服务器,我有我的NSInputStream触发事件,说它有数据读取。 我抓住数据,并build立一个string。
但是,如果我得到的下一个数据块落在utf8数据的一个不幸的位置呢? 就像一个组成的angular色。 看起来好像会混淆string,如果你试图追加一大块非兼容的utf8它。
什么是合适的方式来处理这个问题? 我以为我可以保留数据作为一个NSData,但是我没有办法知道什么时候数据已经完成接收(想想数据长度在头部的HTTP)。
感谢您的任何想法。
您可能要使用的工具是UTF8
。 它将为您处理所有的州问题。 请参阅如何将解密的UInt8转换为string? 一个简单的例子,你可能会适应。
从UTF-8数据构buildstring的主要问题不是组合字符,而是多字节字符。 “拉丁小写字母A”+“组合字母ACCENT”即使分别对这些字符进行解码,也能正常工作。 不工作的是收集你的第一个字节,解码它,然后附加解码的第二个字节。 但UTF8
types将为您处理。 所有你需要做的是将您的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()
可以在这里阻塞,所以它不应该在主队列上调用,除此之外,它是一个标准的发生器,吐出字节。 (这也是可能有很多小angular落案件,我不处理,所以你想仔细考虑这件事,但它并不复杂。)
因此,创build一个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)
)。
最后一个问题是,如果要合并所有的组合标记,例如“拉丁小写字母A”和“组合标记ACCENT”总是一起返回(而不是两个字符)。 这实际上比听起来有点棘手。 首先,你需要生成string,而不是字符。 然后你需要一个很好的方法来知道所有的组合字符是什么。 这当然是可以知道的,但是我得到一个简单algorithm的麻烦。 Cocoa中没有“结合MarkCharacterSet”。 我还在想。 获得“主要工作”的东西很容易,但是我不确定如何构build它,以便它对所有的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) }
和一个小文字看:
这是一些正式的文字 而一些Zalgoi̝̗̹̼n͕͓̘v͇̠͈͕̻̹̫͡o̷͚͍̙͖ke̛̘̜̘͓̖̬组成的东西 还有一行没有换行符
(第二行是一些Zalgo编码的文本 ,这对于testing是很好的。)
我还没有做过任何testing,在一个真正的封锁的情况下,如从networking读取,但它应该基于NSInputStream
工作原理(即它应该阻塞,直到至less有一个字节读取,但应该只是填补用任何可用的缓冲区)。
我做了所有匹配的GeneratorType
以便它可以很容易地插入其他的东西,但是如果你没有使用GeneratorType
,而是使用next() throws -> Self.Element
创build你自己的协议,那么error handling可能会更好next() throws -> Self.Element
。 投掷会使得向堆栈中传播错误变得更容易,但会使得更难以插入for...in
循环中。