2017-05-08 42 views
2

我扩展了swift的数据类型,这样我就可以从一个字符串数组初始化一个实例,然后恢复这些字符串。我有两个版本的初始化器;其中一个按预期工作,另一个则没有。我在这里要求帮助理解非工作版本正在发生的事情。这里是延伸部(其中I注释的一个或另一个的初始化,然后执行测试代码)为什么我的数据初始化程序不能产生预期结果?

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. 在这两种情况下,打印出的编码字符串然而在toStringArray方法枚举数相同
  2. 印刷显示,有一些不同的东西。

回答

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计数 - 这是对的克起始字节的字节索引 iven地区。事实上,它给你的字节区域的长度,当使用后台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,直到bug修复为止,因为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 
    } 
} 
+0

谢谢你,Hamish。 – Verticon

0

我猜Swift将实例化的构造函数Data(with:strings)传递给NSData,并且这不会让您在自我初始化后进行更改。

在您的初始化工具中,您可以对可变实例进行更改,然后将其分配给自我,这就是为什么它可以工作。

即使你已经在你的测试中声明了编码为var,Swift仍然会先将对象构造为一个不可变的实例,然后再将它分配给变量。

您可以解决此通过分配可变实例自我,而不是使用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) 
    } 
} 
+0

数据的append方法被标记为变异。 – Verticon

+0

是的,但是因为它桥接到一个ObjectiveC类,所以方法总是可用的(NSData和NSMutableData的切换发生在运行时),所以Swift编译器并不总是能够确定底层对象是否可变。这也是为什么将NSMutableData分配给自己的工作原理(Swift编译器实际上不知道桥接器会做什么并且假定该方法可用)。 –

+0

Alain,你是说数据被桥接到NSData,NSData是不可变的,因此append方法默默地失败? – Verticon

相关问题