2017-05-08 41 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

相關問題