リフレクションを使わずにデバイスモデル名を取得する方法(Swift)

## はじめに

`iPhone6,1` のようなデバイスモデル名を取得したいことがありますが、ググってもリフレクションを使った記事しか見つかりません。
[Twitterでリフレクションを使わずに取得する方法を教えていただいた](https://twitter.com/trickart4121/status/1687001389288103936) ので紹介します。

## 環境

- OS：macOS Ventura 13.4.1
- Swift：5.8

## デバイスモデル名を取得する方法

デバイスモデル名を取得する方法です。

### リフレクションあり

まずはリフレクションを使う方法です。

以下の記事を参考に実装します。

https://dev.classmethod.jp/articles/get-model-name/

```swift:UIDevice+Model.swift
import UIKit

extension UIDevice {
    static var modelName: String {
        var systemInfo = utsname()
        uname(&systemInfo)

        let mirror = Mirror(reflecting: systemInfo.machine)
        let identifier = mirror.children.reduce("") { identifier, element in
            guard
                let value = element.value as? Int8,
                value != 0
            else {
                return identifier
            }

            return identifier + String(UnicodeScalar(UInt8(value)))
        }

        return identifier
    }
}
```

実際に呼び出せることを確認します。

```swift
print("Device model: \(UIDevice.modelName)")
```

無事にデバイスモデル名が出力されました。

```
Device model: arm64
```

Swift Playgroundsで実行したため `arm64` と出力されています。

### リフレクションなし

次にリフレクションを使わずに実装します。

```swift:UIDevice+Model.swift
import UIKit

extension UIDevice {
    static var modelName: String {
        var systemInfo = utsname()
        uname(&systemInfo)

        let chars: [CChar] = [
            systemInfo.machine.0,
            systemInfo.machine.1,
            systemInfo.machine.2,
            systemInfo.machine.3,
            systemInfo.machine.4,
            systemInfo.machine.5,
            systemInfo.machine.6,
            systemInfo.machine.7,
            systemInfo.machine.8,
            systemInfo.machine.9,
            systemInfo.machine.10,
            systemInfo.machine.11,
            systemInfo.machine.12,
            systemInfo.machine.13,
            systemInfo.machine.14,
            systemInfo.machine.15,
            systemInfo.machine.16,
            systemInfo.machine.17,
            systemInfo.machine.18,
            systemInfo.machine.19,
            systemInfo.machine.20,
            systemInfo.machine.21,
            systemInfo.machine.22,
            systemInfo.machine.23,
            systemInfo.machine.24,
            systemInfo.machine.25,
            systemInfo.machine.26,
            systemInfo.machine.27,
            systemInfo.machine.28,
            systemInfo.machine.29,
            systemInfo.machine.30,
            systemInfo.machine.31,
            systemInfo.machine.32,
            systemInfo.machine.33,
            systemInfo.machine.34,
            systemInfo.machine.35,
            systemInfo.machine.36,
            systemInfo.machine.37,
            systemInfo.machine.38,
            systemInfo.machine.39,
            systemInfo.machine.40,
            systemInfo.machine.41,
            systemInfo.machine.42,
            systemInfo.machine.43,
            systemInfo.machine.44,
            systemInfo.machine.45,
            systemInfo.machine.46,
            systemInfo.machine.47,
            systemInfo.machine.48,
            systemInfo.machine.49,
            systemInfo.machine.50,
            systemInfo.machine.51,
            systemInfo.machine.52,
            systemInfo.machine.53,
            systemInfo.machine.54,
            systemInfo.machine.55,
            systemInfo.machine.56,
            systemInfo.machine.57,
            systemInfo.machine.58,
            systemInfo.machine.59,
            systemInfo.machine.60,
            systemInfo.machine.61,
            systemInfo.machine.62,
            systemInfo.machine.63,
            systemInfo.machine.64,
            systemInfo.machine.65,
            systemInfo.machine.66,
            systemInfo.machine.67,
            systemInfo.machine.68,
            systemInfo.machine.69,
            systemInfo.machine.70,
            systemInfo.machine.71,
            systemInfo.machine.72,
            systemInfo.machine.73,
            systemInfo.machine.74,
            systemInfo.machine.75,
            systemInfo.machine.76,
            systemInfo.machine.77,
            systemInfo.machine.78,
            systemInfo.machine.79,
            systemInfo.machine.80,
            systemInfo.machine.81,
            systemInfo.machine.82,
            systemInfo.machine.83,
            systemInfo.machine.84,
            systemInfo.machine.85,
            systemInfo.machine.86,
            systemInfo.machine.87,
            systemInfo.machine.88,
            systemInfo.machine.89,
            systemInfo.machine.90,
            systemInfo.machine.91,
            systemInfo.machine.92,
            systemInfo.machine.93,
            systemInfo.machine.94,
            systemInfo.machine.95,
            systemInfo.machine.96,
            systemInfo.machine.97,
            systemInfo.machine.98,
            systemInfo.machine.99,
            systemInfo.machine.100,
            systemInfo.machine.101,
            systemInfo.machine.102,
            systemInfo.machine.103,
            systemInfo.machine.104,
            systemInfo.machine.105,
            systemInfo.machine.106,
            systemInfo.machine.107,
            systemInfo.machine.108,
            systemInfo.machine.109,
            systemInfo.machine.110,
            systemInfo.machine.111,
            systemInfo.machine.112,
            systemInfo.machine.113,
            systemInfo.machine.114,
            systemInfo.machine.115,
            systemInfo.machine.116,
            systemInfo.machine.117,
            systemInfo.machine.118,
            systemInfo.machine.119,
            systemInfo.machine.120,
            systemInfo.machine.121,
            systemInfo.machine.122,
            systemInfo.machine.123,
            systemInfo.machine.124,
            systemInfo.machine.125,
            systemInfo.machine.126,
            systemInfo.machine.127,
            systemInfo.machine.128,
            systemInfo.machine.129,
            systemInfo.machine.130,
            systemInfo.machine.131,
            systemInfo.machine.132,
            systemInfo.machine.133,
            systemInfo.machine.134,
            systemInfo.machine.135,
            systemInfo.machine.136,
            systemInfo.machine.137,
            systemInfo.machine.138,
            systemInfo.machine.139,
            systemInfo.machine.140,
            systemInfo.machine.141,
            systemInfo.machine.142,
            systemInfo.machine.143,
            systemInfo.machine.144,
            systemInfo.machine.145,
            systemInfo.machine.146,
            systemInfo.machine.147,
            systemInfo.machine.148,
            systemInfo.machine.149,
            systemInfo.machine.150,
            systemInfo.machine.151,
            systemInfo.machine.152,
            systemInfo.machine.153,
            systemInfo.machine.154,
            systemInfo.machine.155,
            systemInfo.machine.156,
            systemInfo.machine.157,
            systemInfo.machine.158,
            systemInfo.machine.159,
            systemInfo.machine.160,
            systemInfo.machine.161,
            systemInfo.machine.162,
            systemInfo.machine.163,
            systemInfo.machine.164,
            systemInfo.machine.165,
            systemInfo.machine.166,
            systemInfo.machine.167,
            systemInfo.machine.168,
            systemInfo.machine.169,
            systemInfo.machine.170,
            systemInfo.machine.171,
            systemInfo.machine.172,
            systemInfo.machine.173,
            systemInfo.machine.174,
            systemInfo.machine.175,
            systemInfo.machine.176,
            systemInfo.machine.177,
            systemInfo.machine.178,
            systemInfo.machine.179,
            systemInfo.machine.180,
            systemInfo.machine.181,
            systemInfo.machine.182,
            systemInfo.machine.183,
            systemInfo.machine.184,
            systemInfo.machine.185,
            systemInfo.machine.186,
            systemInfo.machine.187,
            systemInfo.machine.188,
            systemInfo.machine.189,
            systemInfo.machine.190,
            systemInfo.machine.191,
            systemInfo.machine.192,
            systemInfo.machine.193,
            systemInfo.machine.194,
            systemInfo.machine.195,
            systemInfo.machine.196,
            systemInfo.machine.197,
            systemInfo.machine.198,
            systemInfo.machine.199,
            systemInfo.machine.200,
            systemInfo.machine.201,
            systemInfo.machine.202,
            systemInfo.machine.203,
            systemInfo.machine.204,
            systemInfo.machine.205,
            systemInfo.machine.206,
            systemInfo.machine.207,
            systemInfo.machine.208,
            systemInfo.machine.209,
            systemInfo.machine.210,
            systemInfo.machine.211,
            systemInfo.machine.212,
            systemInfo.machine.213,
            systemInfo.machine.214,
            systemInfo.machine.215,
            systemInfo.machine.216,
            systemInfo.machine.217,
            systemInfo.machine.218,
            systemInfo.machine.219,
            systemInfo.machine.220,
            systemInfo.machine.221,
            systemInfo.machine.222,
            systemInfo.machine.223,
            systemInfo.machine.224,
            systemInfo.machine.225,
            systemInfo.machine.226,
            systemInfo.machine.227,
            systemInfo.machine.228,
            systemInfo.machine.229,
            systemInfo.machine.230,
            systemInfo.machine.231,
            systemInfo.machine.232,
            systemInfo.machine.233,
            systemInfo.machine.234,
            systemInfo.machine.235,
            systemInfo.machine.236,
            systemInfo.machine.237,
            systemInfo.machine.238,
            systemInfo.machine.239,
            systemInfo.machine.240,
            systemInfo.machine.241,
            systemInfo.machine.242,
            systemInfo.machine.243,
            systemInfo.machine.244,
            systemInfo.machine.245,
            systemInfo.machine.246,
            systemInfo.machine.247,
            systemInfo.machine.248,
            systemInfo.machine.249,
            systemInfo.machine.250,
            systemInfo.machine.251,
            systemInfo.machine.252,
            systemInfo.machine.253,
            systemInfo.machine.254,
            systemInfo.machine.255,
            systemInfo.machine.254,
            systemInfo.machine.255,
        ]

        return String(cString: chars)
    }
}
```

愚直ですが、実際に呼び出せます。

```swift
print("Device model: \(UIDevice.modelName)") // Device model: arm64
```

## 解説

まず `systemInfo.machine` は256つの要素を持ったタプルです。

```swift:Darwin.POSIX.sys.utsname
public struct utsname {
    // ...
    public var machine: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) /* [XSI] Hardware type */
}
```

タプルは `.x` でx番目の要素へアクセスできるため、 `systemInfo.machine.x` と書いていました。
そして `String` への変換はタプルでなく配列で `CChar` を用意する必要があるため、愚直にタプルの要素を1つずつ配列へ格納していた、ということです。

## 余談

`systemInfo.machine.x` は1文字ずつに対応しているので、 `arm64` なら `chars` の中身が `systemInfo.machine.5` まででも問題なく実行できます。

```swift
let chars: [CChar] = [
    systemInfo.machine.0,
    systemInfo.machine.1,
    systemInfo.machine.2,
    systemInfo.machine.3,
    systemInfo.machine.4,
    systemInfo.machine.5,
]
```

では配列の中身が以下ならどう出力されるでしょうか。

```swift
let chars: [CChar] = [
    systemInfo.machine.1,
    systemInfo.machine.2,
    systemInfo.machine.4,
    systemInfo.machine.5,
]
```

答えは以下です。

```swift
print("Device model: \(UIDevice.modelName)") // Device model: rm4
```

1(0)番目の `a` と4(3)番目の `6` が抜けてしまいました。

表にするとわかりやすいです。

|変数|値|文字|
|:--|--:|:--|
| `systemInfo.machine.0` | `97` | `a` |
| `systemInfo.machine.1` | `114` | `r` |
| `systemInfo.machine.2` | `109` | `m` |
| `systemInfo.machine.3` | `54` | `6` |
| `systemInfo.machine.4` | `52` | `4` |
| `systemInfo.machine.5` 〜 `systemInfo.machine.255` | `0` |NULL|

あとは終端がNULL（ `0` ）でないと実行時エラーが発生します。

```swift
let chars: [CChar] = [
    systemInfo.machine.1,
    systemInfo.machine.2,
    systemInfo.machine.4,
]

// ❌: Input of String.init(cString:) must be null-terminated
return String(cString: chars)
```

最初の例は `systemInfo.machine.5` が `0` なので成功しました。
終端は `systemInfo.machine.6` でも `0` でも `.init()` でも問題ありません。

```swift
let chars: [CChar] = [
    systemInfo.machine.1,
    systemInfo.machine.2,
    systemInfo.machine.4,
    systemInfo.machine.5, // `systemInfo.machine.6` や `0` 、 `.init()` などでもOK
]
```

## おわりに

リフレクションを多用するのはよくないですが、タプルを配列へ変換するのに便利ということがわかりました。
リフレクションは用法・用量を守って使っていきたいと思います。