ios - 将流式 (utf8) 数据转换为字符串的安全方法是什么?

标签 ios swift cocoa utf-8

假设我是一个用 objc/swift 编写的服务器。客户端正在向我发送大量数据,这实际上是一个很大的 utf8 编码字符串。作为服务器,我有我的 NSInputStream 触发事件说它有数据要读取。我抓取数据并用它构建一个字符串。

但是,如果我获得的下一个数据 block 落在 utf8 数据中的一个不幸位置怎么办?就像一个沉着的角色。如果您尝试向它附加一大块不兼容的 utf8,它似乎会弄乱字符串。

处理这个问题的合适方法是什么?我在想我可以将数据保留为 NSData,但无论如何我都不知道数据何时完成接收(想想 HTTP,其中数据长度在 header 中)。

感谢任何想法。

最佳答案

您可能想在这里使用的工具是 UTF8 .它将为您处理所有状态问题。参见 How to cast decrypted UInt8 to 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()
    }
}

调用 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))。

最后一个问题是,如果您想组合所有组合标记,那么“LATIN SMALL LETTER A”后跟“COMBINING GRAVE ACCENT”将始终一起返回(而不是作为它们的两个字符)。这实际上比听起来有点棘手。首先,您需要生成字符串,而不是字符。然后你需要一个好方法来知道所有的组合字符是什么。这当然是众所周知的,但我在推导一个简单的算法时遇到了一些麻烦。 Cocoa 中没有“combiningMarkCharacterSet”。我还在考虑。获得“大部分工作”的东西很容易,但我还不确定如何构建它以使其对所有 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 创建了你自己的协议(protocol)。抛出可以更容易地将错误传播到堆栈中,但会使插入 for...in 循环变得更加困难。

关于ios - 将流式 (utf8) 数据转换为字符串的安全方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34595070/

相关文章:

iphone - Chartboost 版本 3.0.4

ios - 在具有 MVVM 架构的 iOS 应用程序中调用 Web 服务的最佳位置是什么?

ios - 解析 - xcode 中的多条件查询问题( objective-c )

objective-c - 在 Cocoa 中分配快捷键/热键

cocoa - 创建一个接受键盘输入的 cocoa 按钮

ios - NSURLConnection 下载进度

swift - Foundation 是默认导入的吗?

ios - 快速文本字段的自定义边框

ios - 使用 iCarousel 正确显示另一个 Storyboard 中的 Controller View

objective-c - 对象数组的内存分配-我的理解有效吗?