【Swift5】Data・UInt8のextension備忘録


+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*―+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*｡ﾟ
iOS Advent Calendar 2019　6日目です！
+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*―+*ﾟ｡*｡ﾟ*+―+*ﾟ｡*｡ﾟ


#はじめに
Bluetooth周りの実装をすることがあり、その時に
DataやUInt8のextensionを作成する機会がありましたので、その備忘録です。

なお、もっと良い書き方や、ご指摘などありましたら、コメントいただけますと幸いです:pray::sparkles:

##そもそもData・UInt8とは何か？

Apple公式によると、それぞれ下記のように定義されています。

- [Data](https://developer.apple.com/documentation/foundation/data)・・・メモリ内のバイトバッファ
- [UInt8](https://developer.apple.com/documentation/swift/uint8)・・・8ビットの符号なし整数値タイプ


それぞれをもう少し砕いた内容が以下になります。

>Dataは、**バイト列を表現するための型**です。
Dataは、**1バイトを表すUInt8型の列としても振る舞い**、
また内部のメモリ領域を指すポインタを触ることができます。
引用：[Swift 3 の Data とポインタ使いこなし術](https://developer.hatenastaff.com/entry/swift-3-foundation-data-and-pointer)

基本的なことですが、デジタルメモリーには以下のような単位があります。(上から小さい順)
>
- ビット（ｂｉｔ）
- バイト（ｂｙｔｅ)
- キロバイト（ｋＢ）
- メガバイト（ＭＢ）
- キガバイト（ＧＢ）
- テラバイト（ＴＢ）

この中で今回は、「ビット」と「バイト」について触れていきます。
特徴は以下のようになります。

- **ビット（ｂｉｔ）**・・・１ビットのメモリーには１又は０を１個だけ記録できる。
- **バイト（ｂｙｔｅ)**・・・８個のビットを１単位として扱うのがバイト。「１バイト=８ビット」です。
![byte.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/6b93411d-b32b-0704-b3d7-e1bf2a02c6ef.png)

UInt8は、8ビットの符号なし整数値タイプなので、
↑の図に付け加えると↓のような位置づけになります。
オレンジのカッコ部分は配列だと思ってください。
![uint8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/e6cecaba-b7be-e1c7-a177-23620cf69c6d.png)

## 基本の関連用語

実装の説明で使用する用語で、初心者にはわかりづらい用語がありましたので、
ここで補足させていただきます。
なお、私自身理解が浅いためすべて引用になりますが、ご了承ください。:bow_tone1::sweat_drops:

####●バッファ
複数の主体がデータを送受信する際に、処理速度や転送速度の差を補うために
データを一時的に蓄えておく記憶装置や記憶領域のこと。
(引用：http://e-words.jp/w/%E3%83%90%E3%83%83%E3%83%95%E3%82%A1.html)

####●バインド
結びつける、関連付ける、などの意味を持つ英単語。
何らかの要素やデータ、ファイルなどが相互に関連付けられている状態や、
そのような状態を実現する機能などのことを指すことが多い。
(引用：http://e-words.jp/w/%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89.html)

####●基数変換
ある基数（n進数のn）で表現された数値を、
別の基数による表現に変換することを基数変換という。
(引用：http://e-words.jp/w/%E5%9F%BA%E6%95%B0.html)

####●ポインタ
何かの位置を指し示すための仕組みや道具などのこと。
プログラミングでは、変数や関数などが置かれたメインメモリ上の
番地などを格納する特殊な変数のことをポインタという。
(引用：http://e-words.jp/w/%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF.html)


それでは、以上を踏まえて、Data周りのextensionを実装していきます。

#実装

##①Dataを10進数のIntに変換

```.swift
extension Data {

    /// 10進数に変換
    var encodedDecimalNumber: Int? {
        let number = map { String(format: "%02hhx", $0) }.joined()
        return Int(number, radix: 16)
    }
}
```

```%02x``` は、2桁まで(01、10等)のフォーマットを示すので、そのフォーマットで各バイトをmapします。
また、```hh```修飾子を使用すると、引数（スタックに整数として渡される）が、
1バイトの量として扱われるそうです。


```Int(number, radix: 16)```では、16進数から10進数に変換しています。
radixには、整数値への変換に使用する[基数](http://e-words.jp/w/%E5%9F%BA%E6%95%B0.html)が設定されます。

[init（_：radix :)](https://developer.apple.com/documentation/swift/int/2924481-init)の引数のradixには、変換に使用する基数を設定するように注意しましょう。

```.swift
// 例
// 07bは１６進数なので、radixには16を設定します。
let hoge = Int("07b", radix: 16)
print(hoge) // 123
```

##②DataをUInt8の配列に変換

```.swift
extension Data {

    /// Data型をUInt8の配列に変換
    var encodedHexadecimals: [UInt8]? {
        let responseValues = self.withUnsafeBytes({ (pointer: UnsafeRawBufferPointer) -> [UInt8] in
            let unsafeBufferPointer = pointer.bindMemory(to: UInt8.self)
            let unsafePointer = unsafeBufferPointer.baseAddress!
            return [UInt8](UnsafeBufferPointer(start: unsafePointer, count: self.count))
        })
        return responseValues
    }
}
```

Data型をUInt8の配列に変換します。

先ほど使用した、
![uint8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186535/e6cecaba-b7be-e1c7-a177-23620cf69c6d.png)
の図をイメージして実装をします。

Dataの[withUnsafeBytes(_:)](https://developer.apple.com/documentation/foundation/data/3139154-withunsafebytes)を使うと、バッファに直接アクセスすることができるそうです。

[bindMemory](https://developer.apple.com/documentation/swift/unsaferawpointer/2428875-bindmemory)は、指定された型にメモリをバインドし、
バインドされたメモリへの型付きポインタを返すメソッドみたいです。
[baseAddress](https://developer.apple.com/documentation/swift/unsafemutablebufferpointer/1539168-baseaddress)は、バッファの最初の要素へのポインタだそうです。

バッファとかポインタとかバインドとか、今後もう少し理解していきたいと思います・・・。:confounded:

##③UInt8(1バイト)をUInt8(1ビット)に変換

```.swift
extension UInt8 {

    /// 1byteでの指定の位置のbitをUInt8に変換
    ///
    /// - Parameter range: Range<Int>
    /// - Returns: UInt8?
    func encodedHexadecimal(range: Range<Int>) -> UInt8? {
        // 2進数に変換
        guard let binary = Int(String(self, radix: 2)) else {
            return nil
        }
        // 8桁0埋め(1byte)の配列にする
        let binaryArray = Array(String(format: "%08d", binary))
        let resultBinary =  binaryArray[range]

        // 2進数に変換
        guard let result = Int(String(resultBinary), radix: 2) else {
            return nil
        }
        return UInt8(result)
    }
}
```

```.swift
// 使用例
let hoge: UInt8 = 0x30 
let fuga = hoge.encodedHexadecimal(range: 2..<3) // 1
print(fuga) // Optional(1)
```

UInt8を2進数に変換する部分では、
[init<T>(_ value: T, radix: Int = 10, uppercase: Bool = false) where T : BinaryInteger](https://developer.apple.com/documentation/swift/string/2997127-init)を使用しています。
ここでは、指定された基数の値を表す文字列を作成して、Int型の2進数に変換しています。

2進数に変更した後、8桁0埋め(1byte)の配列に変換しているのは、桁数を合わせないと、正確な指定位置の値を取得できないためです。
↑の使用例の場合だと、2進数変換直後は、```110000```の6桁なので、
0埋め8桁の```00110000```に変換します。

```.swift
// 例
// 0x0bを10進数に変換するので、radixには10を設定します。
let hoge: UInt8 = 0x0b
print(String(hoge, radix: 10)) // 11
```

配列から指定位置を取得したら、UInt8に変換して返すだけです。

#まとめ
Swiftで開発をしていて、Data周りを今まで調査したことがなかったので、
はじめはDataの変換には抵抗が物凄くありましたが、思っていたより簡単に実装できた印象を持ちました！！
アウトプットすることで、少しつづData周りをイメージしながら、理解も深めていきたいと思います。:raised_hands::sparkles:


#参考
- [メモリーの容量と単位](http://large.rgr.jp/H-aid/ELE/mem/index.html)
-  [Swiftでデータを16進文字列に変換する方法](https://codeday.me/jp/qa/20190127/189231.html)
- [データをSwiftで16進文字列に変換する方法](https://www.code-adviser.com/detail_39075043)
- [Swiftで構造体にデータを読み込む](https://www.code-adviser.com/detail_47525922)