为什么我的数据初始化程序不会产生预期的结果?

时间:2017-05-08 21:55:18

标签: swift swift3

我扩展了swift的数据类型,以便我可以从字符串数组初始化一个实例,然后恢复这些字符串。我有两个版本的初始化程序;其中一个按预期工作,另一个没有。我在这里请求帮助,了解非工作版本的情况。这是扩展名(其中我注释掉一个或另一个初始化程序,然后运行测试代码)

public extension Data {

    var encoding: String.Encoding { return .utf8 }

    // This version works
    public init(with: [String]) {
        let data = NSMutableData()
        with.forEach {
            data.append($0.data(using: String.Encoding.utf8)!)
            data.append([0], length: 1)
        }
        self = data as Data
    }

    // This version does not work
    public init(with: [String]) {
        self.init()
        with.forEach {
            self.append($0.data(using: String.Encoding.utf8)!)
            self.append(0)
        }
    }

    public func toStringArray() -> [String] {
        var decodedStrings = [String]()

        var stringTerminatorPositions = [Int]()

        var currentPosition = 0
        self.enumerateBytes() {
            buffer, count, stop in
            print("Enumeration count = \(count)")
            for i in 0 ..< count {
                if buffer[i] == 0 {
                    stringTerminatorPositions.append(currentPosition)
                }
                currentPosition += 1
            }
        }

        var stringStartPosition = 0
        for stringTerminatorPosition in stringTerminatorPositions {
            let encodedString = self.subdata(in: stringStartPosition ..< stringTerminatorPosition)
            if let decodedString =  String(data: encodedString, encoding: encoding) {
                decodedStrings.append(decodedString)
            }
            stringStartPosition = stringTerminatorPosition + 1
        }

        return decodedStrings
    }
}

这是测试代码:

    let strings = ["one", "two", "three", "four"]
    let encoded = Data(with: strings)
    let decoded = encoded.toStringArray()
    print("\(encoded as NSData) => \(decoded)")

以下是使用工作初始化程序时的输出:

Enumeration count = 19

<6f6e6500 74776f00 74687265 6500666f 757200> => ["one", "two", "three", "four"]

以下是使用非工作初始化程序时的输出:

Enumeration count = 0

<6f6e6500 74776f00 74687265 6500666f 757200> => []

请注意以下事项:

  1. 在这两种情况下,编码字符串的打印都是相同的
  2. 然而,toStringArray方法中的枚举计数的打印显示存在不同的东西。

2 个答案:

答案 0 :(得分:0)

我猜Swift将构造函数Data(带:strings)桥接到NSData进行实例化,这样就不会让你在self初始化后进行更改。

在你的初始化程序中,你可以对可变实例进行更改,然后将其分配给self,这就是它工作的原因。

即使您在测试中声明编码为var,Swift仍然会在将对象分配给变量之前将其首先构造为不可变实例。

您可以通过将可变实例分配给self而不是使用self.init()来解决此问题:

// This version will now work
public init(with: [String]) 
{
    self = NSMutableData() as Data
    with.forEach 
    {
        self.append($0.data(using: String.Encoding.utf8)!)
        self.append(0)
    }
}

答案 1 :(得分:0)

您的初始化程序没有任何问题 - 问题在于您使用enumerateBytes

self.enumerateBytes() {
    buffer, count, stop in

    print("Enumeration count = \(count)")
    for i in 0 ..< count {
        if buffer[i] == 0 {
            stringTerminatorPositions.append(currentPosition)
        }
        currentPosition += 1
    }
}

你在这里调用count的是计数 - 它是给定区域的起始字节的字节索引。事实上,它为您提供了使用后备NSData存储的字节区域的长度is a bug(将在下一个Swift版本中修复)。

要获取字节区域的计数,您只想说buffer.count - 或者假设UnsafeBufferPointerSequence,您还可以说:

var currentPosition = 0
self.enumerateBytes { buffer, byteIndex, stop in

    for byte in buffer {
        if byte == 0 {
            stringTerminatorPositions.append(currentPosition)
        }
        currentPosition += 1
    }
}

或者更简单:

self.enumerateBytes { buffer, byteIndex, stop in

    for (offset, byte) in buffer.enumerated() where byte == 0 {
        stringTerminatorPositions.append(byteIndex + offset)
    }
}

虽然实现目前不适用于由Data支持的NSData个实例,直到错误修复为止,因为byteIndex会错误地为您提供长度该地区。

但是如果你稍后在实现中调用subdata(in:),它会将每个字符串的给定字节复制到一个新的缓冲区中 - 我真的没有看到在这里使用enumerateBytes的优势。您可以通过使用withUnsafeBytes来简化您的实现,以便为您提供连续的数据视图:

public func cStringsToArray(encoding: String.Encoding = .utf8) -> [String] {

    return withUnsafeBytes { (ptr: UnsafePointer<Int8>) in

        var strings = [String]()
        var previous = ptr

        for offset in 0 ..< count {

            let current = ptr + offset

            if current != previous && current.pointee == 0 {
                // if we cannot decode the string, append a unicode replacement character
                // feel free to handle this another way.
                strings.append(String(cString: previous, encoding: encoding) ?? "\u{FFFD}")
                previous = current + 1
            }
        }

        return strings
    }
}