iOSで5Gかどうか判断する（罠付き）

# 前提

Xcode Version 12.4 (12D4e)で動作確認しています。

# 概要

iOSでCoreTelephonyを使って、今使っているキャリアが5Gかどうかを判別します。
また、その際の罠について紹介します。

# 結論

iOS 14.1以上で、5Gかどうかの判断をするようにしましょう。

## 今使っているキャリアを取得

まず、`CTTelephonyNetworkInfo`を生成します。
CTTelephonyNetworkInfoから`serviceSubscriberCellularProviders` を使って端末のキャリアを取得します。
keyとともに `CTCarrier` を取得できます。

```swift
        let info = CTTelephonyNetworkInfo()
        info.serviceSubscriberCellularProviders?.forEach({ (key, value) in
            print(value.carrierName ?? "")
        })
```

## 無線テクノロジーを取得
https://developer.apple.com/documentation/coretelephony/cttelephonynetworkinfo/radio_access_technology_constants

先ほど得たキーで、`serviceCurrentRadioAccessTechnology`より無線のテクノロジーが何であるかを取得可能です。
CoreTelephonyのドキュメントに `Radio Access Technology Constants`があるので、switchを使って目的の無線テクノロジーを検出できます。

`CTRadioAccessTechnologyNR`,`CTRadioAccessTechnologyNRNSA`は5Gになります。
二つの違いはスタンドアローン型か非スタンドアローン型かの違いです。詳しくはWikipediaを貼っておきます。
https://ja.wikipedia.org/wiki/5G_NR#展開方式

```swift
        let info = CTTelephonyNetworkInfo()
        info.serviceSubscriberCellularProviders?.forEach({ (key, value) in
            if let radioAccessTechnology = info.serviceCurrentRadioAccessTechnology?[key] {
                switch radioAccessTechnology {
                case CTRadioAccessTechnologyLTE:
                    print("これはLTEです")
                case CTRadioAccessTechnologyNRNSA:
                    print("これは非スタンドアローンモード 5Gです")
                case CTRadioAccessTechnologyNR:
                    print("スタンドアローンモード 5Gです")
                default:
                    print(radioAccessTechnology)
                }
            }
        })
```


## iOS 13以下への対応

しかしながら、開発中のアプリがiOS13以降にも対応するとなると、このコードだとエラーが起きます。

![スクリーンショット 2021-03-25 15.10.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687960/fdcc60f9-d807-3ed6-7770-9e90d1876379.png)

`'CTRadioAccessTechnologyNRNSA' is only available in iOS 14.0 or newer`
ということなので、version checkのコードを追加します。

```swift
        let info = CTTelephonyNetworkInfo()
        info.serviceSubscriberCellularProviders?.forEach({ (key, value) in
            if let radioAccessTechnology = info.serviceCurrentRadioAccessTechnology?[key] {
                switch radioAccessTechnology {
                case CTRadioAccessTechnologyLTE:
                    print("これはLTEです")
                default:
                    if #available(iOS 14.0, *) {
                        switch radioAccessTechnology {
                        case CTRadioAccessTechnologyNRNSA:
                            print("これは非スタンドアローンモード 5Gです")
                        case CTRadioAccessTechnologyNR:
                            print("スタンドアローンモード 5Gです")
                        default:
                            print(radioAccessTechnology)
                        }
                    } else {
                        print(radioAccessTechnology)
                    }
                }
            }
        })
```

### ここから罠
実はこのコード、**iOS 14.0系のOSで実行するとクラッシュします**。
理由はCTRadioAccessTechnologyNRNSAとCTRadioAccessTechnologyNRの部分で、
実はこの二つの定数はiOS 14.1からのAPIであるとの記載がWebのドキュメントにあります。

https://developer.apple.com/documentation/coretelephony/ctradioaccesstechnologynrnsa

https://developer.apple.com/documentation/coretelephony/ctradioaccesstechnologynr

しかしながら、Xcodeで定義にジャンプするとこれらのAPIは `@available(iOS 14.0, *)` でマークされています。
このため、対応OSと定義で不整合が起きて、iOS 14.0に本来無いAPIに触ろうとしてクラッシュするわけです。

### 対策
`#available` の部分を iOS 14.1に変更しましょう。
これで本来APIが使えるOSだけがAPIに触ることになります。

```swift
        let info = CTTelephonyNetworkInfo()
        info.serviceSubscriberCellularProviders?.forEach({ (key, value) in
            if let radioAccessTechnology = info.serviceCurrentRadioAccessTechnology?[key] {
                switch radioAccessTechnology {
                case CTRadioAccessTechnologyLTE:
                    print("これはLTEです")
                default:
                    if #available(iOS 14.1, *) {
                        switch radioAccessTechnology {
                        case CTRadioAccessTechnologyNRNSA:
                            print("これは非スタンドアローンモード 5Gです")
                        case CTRadioAccessTechnologyNR:
                            print("スタンドアローンモード 5Gです")
                        default:
                            print(radioAccessTechnology)
                        }
                    } else {
                        print(radioAccessTechnology)
                    }
                }
            }
        })
```

#### ちなみに
Xcode Version 12.5 beta 3 (12E5244e)だと、この問題は解決されていて、CTRadioAccessTechnologyNRNSAとCTRadioAccessTechnologyNRは `@available(iOS 14.1, *)` でマークされています。