ごらくらいふ

プログラミングしたりゲームしたり

iOSでキャリア情報の取得が大変。

データ通信に使っているキャリアの情報が欲しい

iPhone XS, iPhone XS Max, iPhone XR以降、nano-SIMとeSIMを使ったデュアルSIMに対応している。*1

この機能はiOS 12.1 から利用できるそうだ。 これに合わせて、CTTelephonyNetworkInfoAPI が更新された。 *2 *3

また、iOS 13からはデュアルSIMの両方で通話が利用できる。 このうちデータ通信は一つの回線だけ利用可能で、それを取得するための API が追加された。 *4

この状況下で、「いったいどのキャリアがデータ通信に使用されているのかを知りたい。」場合の話。

iOS 12.0 では新しい API からキャリア情報が得られない

そういう問題があるらしい。*5 しかし API 上は subscriberCellularProviderdeprecated になってしまった。

軽く調べたところ解決法はすでに見つかっていて、private property によって取得できるらしい。 (deprecated なメソッドを使うのとどっちもどっちな気がしないでもない。)

最終的にこうなる

  • extension をつかって CTTelephonyNetworkInfo を拡張した。
  • iOS 13.0 未満においては、キャリア情報が一つかどうかでデータ通信に使用中か判定する。
extension CTTelephonyNetworkInfo {
    typealias Using = Bool

    /// iOS のバージョンに応じてキャリア情報と、それが通信に使用可能なものか取得する
    var versionCompatibleServiceSubscriberCellularProviders: [(CTCarrier, Using)] {
        if #available(iOS 13.0, *) {
            // iOS 13 からデュアルSIMを同時利用可能 https://support.apple.com/ja-jp/HT209086
            // データ通信に使用するSIMは iOS 13 から判別可能 https://developer.apple.com/documentation/coretelephony/cttelephonynetworkinfo/3183044-dataserviceidentifier
            guard let providors = self.serviceSubscriberCellularProviders else { return [] }
            guard providors.count > 0 else { return [] }

            let usingId = self.dataServiceIdentifier ?? ""
            return providors.map { (key: String, carrier: CTCarrier) in
                let using = key == usingId
                return (carrier, using)
            }
        } else if #available(iOS 12.1, *) {
            // iOS 12 から複数のSIMがありうる https://developer.apple.com/documentation/coretelephony/cttelephonynetworkinfo/3024511-servicesubscribercellularprovide
            // iOS 12.0 ではバグで値が得られないらしい https://stackoverflow.com/questions/52846542/why-do-servicesubscribercellularproviders-return-nil-in-ios-12
            guard let providers = self.serviceSubscriberCellularProviders else { return [] }
            guard providers.count > 0 else { return [] }

            // 1件であれば使っていることが自明
            let using = providers.count == 1
            return providers.map { (key: String, carrier: CTCarrier) in
                return (carrier, using)
            }
        } else if #available(iOS 12.0, *) {
            // private property によって参照できるらしい
            // microsoft の app-center-sdk におけるissue https://github.com/microsoft/appcenter-sdk-apple/issues/1905
            // microsoft の app-center-sdk にも採用されている https://github.com/microsoft/appcenter-sdk-apple/pull/1914/files/fa504f5278f7ca98a3587479d3adcfd4d568922b
            guard let providers = self.value(forKey: "serviceSubscriberCellularProvider") as? Dictionary<String, CTCarrier> else { return [] }
            guard providers.count > 0 else { return [] }

            // 1件であれば使っていることが自明
            let using = providers.count == 1
            return providers.map { (key: String, carrier: CTCarrier) in
                return (carrier, using)
            }
        } else {
            guard let carrier = self.subscriberCellularProvider else { return [] }

            return [(carrier, true)]
        }
    }
}

参考リンク