Blog スタッフブログ

iOS Swift システム開発

[iOS]iOS17から用途明記が必須になるAPI、全種使ってみた

Swift

こんにちは、株式会社MIXシステム開発担当のBloomです。

iOS17では一部のAPIについてフィンガープリントの取得目的のAPIの利用を防ぐべく、新たにPrivacyInfo.xcprivacyという設定ファイルへ利用用途についての記載が必要になりました。

今回はそのAPI群を全種実装し、それぞれの使い方や選択できる利用用途を確認していきたいと思います。

まずはプロジェクトに[New File…]からPrivacyInfo.xcprivacyを追加しておきましょう。

追加したxcprivacyファイルにはこのような形式になります。NSPrivacyAccessedAPITypeでは対象APIを、NSPrivacyAccessedAPITypeReasonsには対応する利用用途を記載します。

では、早速APIを紹介していきます。

File timestamp APIs

その名の通りファイルのタイムスタンプを含む情報を取得するためのAPI群です。FileManagerで取得する方法のほかに、UIDocumentsから取得する方法なども申請が必要になりました。

// ファイルをなにか作ります
if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
    let fileURL = documentsDirectory.appendingPathComponent("example.txt")
    let text = "example"
    try? text.write(to: fileURL, atomically: true, encoding: .utf8)
    
    // 生成したファイルのタイムスタンプを取得します
    if let attr = try? FileManager.default.attributesOfItem(atPath: fileURL.path) {
        if let creationDate = attr[.creationDate] as? NSDate,
            let modificationDate = attr[.modificationDate] as? NSDate {
            print(creationDate)
            print(modificationDate)
        }
    }
}

このコードを実行すると生成したファイルのタイムスタンプが表示されます。

では、PrivacyInfo.xcprivacyに利用用途を明記しましょう。選択できる用途には以下の種類があります。

DDA9.1
Declare this reason to display file timestamps to the person using the device.
Information accessed for this reason, or any derived information, may not be sent off-device.

端末を利用しているユーザにファイルのタイムスタンプを表示するために利用する場合はこの理由を選びます。
この理由によって取得したあらゆる情報は、端末外に送信してはいけません。
C617.1
Declare this reason to access the timestamps of files inside the app container, app group container, or the app’s CloudKit container.

AppGroupなどで共有しているファイルやCloudKitのコンテナで管理しているファイルのタイムスタンプを取得したいならこの理由を選びます。
3B52.1
Declare this reason to access the timestamps of files or directories that the user specifically granted access to, such as using a document picker view controller.

UIDocumentPickerViewControllerなどを利用しておりユーザが明示的に利用を許可したファイルのタイムスタンプを表示したいならこの理由を選びましょう。

System boot time APIs

システム起動時間を取得するためのAPI群です。

let uptime = ProcessInfo.processInfo.systemUptime
print(uptime) 

このコードを実行すると端末を再起動してからの時間が秒数で表示されます。

選択できる利用用途は下記の一つのみです。

35F9.1
Declare this reason to access the system boot time in order to measure the amount of time that has elapsed between events that occurred within the app or to perform calculations to enable timers.
Information accessed for this reason, or any derived information, may not be sent off-device. There is an exception for information about the amount of time that has elapsed between events that occurred within the app, which may be sent off-device.

アプリ内においての経過時間を測定するため、またはタイマーの利用に伴う計算を実行するために、システム起動時間にアクセスするためにこの理由を宣言します。
この理由によって取得したあらゆる情報は、端末外に送信してはいけません。ただし、これを利用して算出したアプリ内においての経過時間に関する情報は例外でデバイス外に送信することができます。

Disk space APIs

ストレージの空き容量を取得するAPI群です。下記で紹介するresourceValuesの利用のほか、FileManagerからsystemFreeSizeを利用しての取得も同様に申請が必要なので気をつけましょう。

let result = try? url.resourceValues(
    forKeys: [
        .volumeTotalCapacityKey,
        .volumeAvailableCapacityKey,
        .volumeAvailableCapacityForImportantUsageKey,
        .volumeAvailableCapacityForOpportunisticUsageKey,
    ])
 if let result = result,
    let volumeTotalCapacity = result.volumeTotalCapacity,
    let volumeAvailableCapacity = result.volumeAvailableCapacity,
    let volumeAvailableCapacityForImportantUsage = result.volumeAvailableCapacityForImportantUsage,
    let volumeAvailableCapacityForOpportunisticUsage = result.volumeAvailableCapacityForOpportunisticUsage {
     print(volumeTotalCapacity)
     print(volumeAvailableCapacity)
     print(volumeAvailableCapacityForImportantUsage)
     print(volumeAvailableCapacityForOpportunisticUsage)
 }

このコードを実行するとストレージ容量byte数、空き容量byte数、重要なリソースを格納可能な容量byte数、そうでないリソースを格納可能な容量byte数が出力されます。

選択できる用途は下記の通りになります。

85F4.1
Declare this reason to display disk space information to the person using the device. Disk space may be displayed in units of information (such as bytes) or units of time combined with a media type (such as minutes of HD video).
Information accessed for this reason, or any derived information, may not be sent off-device.

端末を利用しているユーザにストレージ容量情報を表示するためにこのAPIを利用する場合はこの理由を選びます。この容量は、情報単位(byte単位など)やメディアの種類ごとの時間単位(HDビデオを保存できる分数の単位など)で表示することができます。
この理由によって取得したあらゆる情報は、端末外に送信してはいけません。


E174.1
Declare this reason to check whether there is sufficient disk space to write files, or to check whether the disk space is low so that the app can delete files when the disk space is low. The app must behave differently based on disk space in a way that is observable to users.
Information accessed for this reason, or any derived information, may not be sent off-device. There is an exception that allows the app to avoid downloading files from a server when disk space is insufficient.

ファイルを書き込むのに十分なストレージ容量があるかどうかを確認するため、または容量が少ないときにアプリでファイルを削除できるようにしたいときはこの理由を宣言します。アプリはユーザに分かる方法で、容量に余裕があるかそれとも僅少であるかによって振る舞いを変える必要があります。
この理由によって取得したあらゆる情報は、端末外に送信してはいけません。ただし例外として、空き容量が不足している場合にアプリがサーバからファイルをダウンロードしないようにする制御を行うことは許可されています。

Active keyboard APIs

端末で使用できるキーボードの一覧を取得できるAPIです。非公開APIを利用すると画面上で表示されているキーボードの取得も可能で、このサンプルでは改行ボタンタップをトリガーに使っているキーボードのIDを出力しています。

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    let activeInputModes = UITextInputMode.activeInputModes
    let displayedModes = activeInputModes.filter { $0.value(forKey: "isDisplayed") as? Int == 1 }
    if let displayedMode = displayedModes.first,
        let identifier = displayedMode.value(forKey: "identifier") as? String {
        print(identifier)
    }
    return true
}

カスタムキーボードの利用判別もできてしまいます。このサンプルを実行した場合、標準の英字キーボードでは”en_JP@hw=US;sw=QWERTY”が出力されますが、Gboardを利用中は”com.google.keyboard.KeyboardExtension”が出力されます。

このAPIのために選択できる用途を見てみましょう。

3EC4.1
Declare this reason if your app is a custom keyboard app, and you access this API category to determine the keyboards that are active on the device.
Providing a systemwide custom keyboard to the user must be the primary functionality of the app.
Information accessed for this reason, or any derived information, may not be sent off-device.

アプリがカスタムキーボードアプリであり、例えばユーザに端末上でキーボードを設定させるためにこの API を利用したい場合はこの理由を宣言します。
この理由を選択する場合はアプリの主要な機能がユーザにシステム全体のカスタムキーボードを提供することである必要があります。
この理由によって取得したあらゆる情報は、端末外に送信してはいけません。


54BD.1
Declare this reason to access active keyboard information to present the correct customized user interface to the person using the device. The app must have text fields for entering or editing text and must behave differently based on active keyboards in a way that is observable to users.
Information accessed for this reason, or any derived information, may not be sent off-device.

利用しているキーボードを判別してユーザに異なる最適化したUIを提供するためにこのAPIを利用したい場合はこの理由を宣言します。この理由を選択したアプリではテキストを入力または編集するための入力欄を持つ必要があり、ユーザに分かるようにアクティブキーボード情報に基づいて異なるUIを表示するなどする必要があります。
この理由によって取得したあらゆる情報は、端末外に送信してはいけません。

User defaults APIs

お馴染みのUserDefaultsも今回申請が必須になりました。ほとんどのアプリでPrivacyInfoの対応が必要でしょう。

 let exampleKey = "exampleKey"
 UserDefaults.standard.setValue("example", forKey: exampleKey)
 let value = UserDefaults.standard.string(forKey: exampleKey)
 print(value)

保存した文字列をそのまま読み出すだけのサンプルです。早速選択できる用途を見てみましょう。

CA92.1
Declare this reason to access user defaults to read and write information that is only accessible to the app itself.
This reason does not permit reading information that was written by other apps or the system, or writing information that can be accessed by other apps.

アプリ自身のみがアクセス可能な情報を読み書きする、UserDefaultsを利用する場合はこの理由を宣言します。
他のアプリやシステムによって書き込まれた情報の読み取りや、他のアプリがアクセスできる情報の書き込みはこの理由を宣言しても許可されません。

いかがでしたでしょうか。設定ファイルへの追記対応を行わない場合2024年の春ごろからリジェクト対象になる可能性があるため、この記事を参考に審査対策を行なっていけると思います。良かったですね。

参考文献

Describing use of required reason API – developer.apple.com