web-dev-qa-db-ja.com

データとの間の往復Swift数値型

Swift 3が[UInt8]ではなくDataに傾いているので、さまざまな数値型(UInt8、Double、Float、Int64など)をエンコード/デコードする最も効率的で慣用的な方法を見つけようとしています。データオブジェクト。

[UInt8]を使用するためのこの回答 がありますが、DataにはないさまざまなポインターAPIを使用しているようです。

基本的に、次のようなカスタム拡張機能をいくつか作成します。

let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13

私が本当に私を避けている部分、私はドキュメントの束を調べましたが、どのような基本的な構造体(すべての数字です)から何らかのポインタのもの(OpaquePointerまたはBufferPointerまたはUnsafePointer?)を取得できるかです。 Cでは、その前にアンパサンドをたたくだけで、そこに行きます。

80
Travis Griggs

注:コードはSwift 5(Xcode 10.2)向けに更新されました。 (Swift 3およびSwift 4.2バージョンは、編集履歴にあります。)また、アライメントされていないデータが正しく処理されるようになりました。

値からDataを作成する方法

Swift 4.2の時点では、値からデータを簡単に作成できます。

let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }

print(data as NSData) // <713d0ad7 a3104540>

説明:

  • withUnsafeBytes(of: value) は、値の生バイトをカバーするバッファーポインターでクロージャーを呼び出します。
  • 生のバッファポインタはバイトシーケンスであるため、 Data($0) を使用してデータを作成できます。

Dataから値を取得する方法

Swift 5の時点で、DatawithUnsafeBytes(_:) は、バイトに対して「型なし」のUnsafeMutableRawBufferPointerでクロージャーを呼び出します。 load(fromByteOffset:as:) メソッドは、メモリから値を読み取ります:

let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
    $0.load(as: Double.self)
}
print(value) // 42.13

このアプローチには1つの問題があります。メモリがそのタイプのプロパティalignedであることが必要です(ここでは、8バイトアドレスにアラインされています)。しかし、それは保証されていません、例えばデータが別のData値のスライスとして取得された場合。

したがって、バイトを値にcopyする方が安全です。

let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13

説明:

  • withUnsafeMutableBytes(of:_:) は、値の生バイトをカバーする可変バッファーポインターでクロージャーを呼び出します。
  • DataProtocolcopyBytes(to:) メソッド(Dataが準拠)は、データからそのバッファーにバイトをコピーします。

copyBytes()の戻り値は、コピーされたバイト数です。これは、宛先バッファーのサイズに等しいか、データに十分なバイトが含まれていない場合はそれより小さくなります。

汎用ソリューション#1

上記の変換は、struct Dataの汎用メソッドとして簡単に実装できます。

extension Data {

    init<T>(from value: T) {
        self = Swift.withUnsafeBytes(of: value) { Data($0) }
    }

    func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
        var value: T = 0
        guard count >= MemoryLayout.size(ofValue: value) else { return nil }
        _ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
        return value
    }
}

制約T: ExpressibleByIntegerLiteralをここに追加して、値を「ゼロ」に簡単に初期化できるようにします。これは、このメソッドが「trival」(整数および浮動小数点)タイプで使用できるため、実際には制限ではありません。

例:

let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>

if let roundtrip = data.to(type: Double.self) {
    print(roundtrip) // 42.13
} else {
    print("not enough data")
}

同様に、arraysDataに変換したり戻したりできます:

extension Data {

    init<T>(fromArray values: [T]) {
        self = values.withUnsafeBytes { Data($0) }
    }

    func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
        var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
        _ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
        return array
    }
}

例:

let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData) // <0100ff7f 0080>

let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]

汎用ソリューション#2

上記のアプローチには1つの欠点があります。実際には、整数や浮動小数点型などの「単純な」型でのみ機能します。 ArrayStringなどの「複雑な」型には、基礎となるストレージへの(隠された)ポインターがあり、構造体自体をコピーするだけでは渡すことができません。また、実際のオブジェクトストレージへの単なるポインタである参照型では機能しません。

その問題を解決します

  • Dataに変換して戻す方法を定義するプロトコルを定義します。

    protocol DataConvertible {
        init?(data: Data)
        var data: Data { get }
    }
    
  • プロトコル拡張のデフォルトメソッドとして変換を実装します。

    extension DataConvertible where Self: ExpressibleByIntegerLiteral{
    
        init?(data: Data) {
            var value: Self = 0
            guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
            _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
            self = value
        }
    
        var data: Data {
            return withUnsafeBytes(of: self) { Data($0) }
        }
    }
    

    ここでfailableイニシャライザを選択しました。これは、指定されたバイト数が型のサイズと一致することを確認します。

  • 最後に、安全にDataに変換して戻すことができるすべての型への適合を宣言します。

    extension Int : DataConvertible { }
    extension Float : DataConvertible { }
    extension Double : DataConvertible { }
    // add more types here ...
    

これにより、変換がさらにエレガントになります。

let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>

if let roundtrip = Double(data: data) {
    print(roundtrip) // 42.13
}

2番目のアプローチの利点は、安全でない変換を誤って実行できないことです。欠点は、すべての「安全な」型を明示的にリストする必要があることです。

また、次のような重要な変換を必要とする他のタイプのプロトコルを実装することもできます。

extension String: DataConvertible {
    init?(data: Data) {
        self.init(data: data, encoding: .utf8)
    }
    var data: Data {
        // Note: a conversion to UTF-8 cannot fail.
        return Data(self.utf8)
    }
}

または、独自の型に変換メソッドを実装して必要な処理を実行し、値をシリアル化および逆シリアル化します。

バイトオーダー

上記のメソッドではバイト順の変換は行われません。データは常にホストのバイト順です。プラットフォームに依存しない表現(「ビッグエンディアン」「ネットワーク」バイトオーダーなど)の場合、対応する整数プロパティまたはイニシャライザ。例えば:

let value = 1000
let data = value.bigEndian.data
print(data as NSData) // <00000000 000003e8>

if let roundtrip = Int(data: data) {
    print(Int(bigEndian: roundtrip)) // 1000
}

もちろん、この変換は一般的な変換方法でも一般的に実行できます。

203
Martin R

withUnsafePointer を使用すると、mutableオブジェクトへの安全でないポインターを取得できます。

withUnsafePointer(&input) { /* $0 is your pointer */ }

Inout演算子は可変オブジェクトでのみ機能するため、不変オブジェクト用に取得する方法がわかりません。

これは、リンクした回答に示されています。

3
zneak

私の場合、 Martin R の答えは役に立ちましたが、結果は逆になりました。そこで、彼のコードに小さな変更を加えました。

extension UInt16 : DataConvertible {

    init?(data: Data) {
        guard data.count == MemoryLayout<UInt16>.size else { 
          return nil 
        }
    self = data.withUnsafeBytes { $0.pointee }
    }

    var data: Data {
         var value = CFSwapInt16HostToBig(self)//Acho que o padrao do IOS 'e LittleEndian, pois os bytes estavao ao contrario
         return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
    }
}

この問題は、LittleEndianおよびBigEndianに関連しています。

2
Beto Caldas