D.C*_*.C. 4 cocoa utf-8 ios swift
假设我是用objc/swift编写的服务器.客户端正在向我发送大量数据,这实际上是一个很大的utf8编码字符串.作为服务器,我有我的NSInputStream触发事件,说它有数据要读取.我抓住数据并用它构建一个字符串.
但是,如果下一个数据块落在utf8数据中的不幸位置怎么办?喜欢组成的角色.如果你试图将一块不合规的utf8附加到它上面,似乎会弄乱字符串.
有什么方法可以解决这个问题?我当时认为我可以将数据保存为NSData,但是我无论如何都不知道数据何时被接收(想想数据长度在标题中的HTTP).
谢谢你的任何想法.
你可能想在这里使用的工具是UTF8.它将为您处理所有州的问题.请参见如何将解密的UInt8转换为String?一个简单的例子,你可能会适应.
从UTF-8数据构建字符串的主要问题不是组成字符,而是多字节字符."LATIN SMALL LETTER A"+"COMBINING 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()
}
}
Run Code Online (Sandbox Code Playgroud)
调用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
}
}
}
Run Code Online (Sandbox Code Playgroud)
您当然可以将其简单地转换为CharacterGenerator(使用Character(_:UnicodeScalar)).
最后一个问题是如果你想要组合所有组合标记,那么"LATIN SMALL LETTER A"后跟"COMBINING GRAVE ACCENT"将始终一起返回(而不是它们是两个字符).这实际上比听起来有点棘手.首先,您需要生成字符串,而不是字符.然后你需要一个很好的方法来了解所有组合字符是什么.这当然是可以知道的,但我在获得一个简单的算法时遇到了一些麻烦.Cocoa中没有"combineMarkCharacterSet".我还在考虑它.获得"大多数工作"的东西很容易,但我不确定如何构建它以便它对所有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)
}
Run Code Online (Sandbox Code Playgroud)
还有一些文字要读:
Here is some normalish álfa?? text And some Zalgo i??????n???v????????o?????ke???????? composed stuff And one more line with no newline
(第二行是一些Zalgo编码文本,非常适合测试.)
我没有在真正的阻塞情况下对此进行任何测试,例如从网络读取,但它应该根据工作方式NSInputStream工作(即它应该阻塞,直到至少有一个字节要读取,但是然后应该只填充缓冲区用任何可用的东西).
我已经完成了所有这些匹配,GeneratorType以便它可以轻松地插入其他内容,但如果您不使用GeneratorType而错误处理可能会更好,而是创建自己的协议next() throws -> Self.Element.抛出可以更容易地将错误传播到堆栈中,但会使插入for...in循环变得更加困难.