Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

ヤプリiOSエンジニアの新プライバシー要件対応③ | ノーコードアプリプラットフォームの運用体制も大公開!

はじめに

こんにちは、ヤプリでiOSエンジニアをしている菅(@Nao_RandDです。

Appleの新しいプライバシー要件のアップデート に関する記事 「第3弾」 です!

これまでに、2つの記事でそれぞれの読者を対象に、プライバシーマニフェスト1についてや、ヤプリでの取り組みを紹介させていただきました。

  • 第1弾:新しいプライバシー要件がどういったものかと、時系列にそって何があったかを知りたい方に! tech.yappli.io

  • 第2弾:新しいプライバシー要件にヤプリで取り組んだ内容を大公開、企業がどんな対応をしているか知りたい方に! tech.yappli.io

最後を飾る第3弾では、ノーコードのアプリプラットフォームを提供している背景を踏まえて、新しいプライバシー要件に対して会社の運用体制を整備した内容をご紹介します。

何をする必要があるの?

第2弾の記事でサードパーティSDKに対して必要なアップデートを行い、本体プロジェクトにプライバシーマニフェストを追加しました。

1つのアプリを提供する場合には、それ以上の対応は不要かと思いますが、アプリプラットフォームを提供するヤプリでは追加で考慮すべきことがあります。

それは、アプリごとに異なるプライバシーマニフェスト項目を柔軟に変更してビルドできるようにすることです。

アプリプラットフォームとは?

アプリプラットフォームがどういったものか前提の説明をします。

ヤプリでは、プログラミング不要でネイティブアプリを作成・配信できるアプリプラットフォーム「Yappli」を提供しています。

1つのコードベースから800以上のアプリをビルドして、ネイティブアプリとしてストア公開が可能です。 yapp.li

アプリプラットフォームを支えているのは、ビルドBotを含めたビルド基盤です。

これによって、800以上のアプリを手動でビルドする必要なく、リリース・保守が行える体制を整備することができています。

ビルド基盤について
ビルド基盤に関しては、iOSDCでヤプリの @k_katsumiが発表しておりますので、詳細な構成含めてご興味ある方はぜひご覧ください。
youtu.be

ビルド基盤では、弊社で作成したSlackアプリ「ビルドBot」から対象アプリを指定してボタンを押すだけで、CI/CD ツールから必要な情報をサーバー経由で取得して IPA を App Store Connect にアップロードしてくれます。

そのため、アプリをストア公開するのに Xcode や CI/CD ツールに直接触れる必要がなく、エンジニア不要でアプリ公開まで進めることができます。

800以上のアプリを保守していくため、ヤプリではアプリ申請のプロ集団といえる「申請チーム」が存在し、App Store 公開前の審査など必要な手続きを専門で進めてくれます。

アプリのストア公開までの流れ

ヤプリでは以下のような流れで、アプリ制作 → ストア公開まで進行していきます。

アプリ制作をクライアントがアプリプラットフォーム Yappli で行い、開発ディレクターも必要に応じてサポートしながらアプリ公開に必要な情報を準備します。

そして、アプリ申請チームがビルドBotからビルドを実行して App Store Connect に IPA がアップロードされるのを確認し、必要情報を App Store Connect に入力して審査に提出するという流れです。

各アプリで組み替える必要があるプライバシーマニフェストの項目

プライバシーマニフェストは以下の4つの項目から構成されています。

  • Privacy Accessed API Types
  • Privacy Nutrition Label Types
  • Privacy Tracking Enabled
  • Privacy Tracking Domains

参考:Privacy manifest files

アプリごとに組み替える必要があるのは、「Privacy Nutrition Label Types」です。

元々あった App Privacy という、アプリの使用データや用途を説明する項目と対応しています。

developer.apple.com

アプリでどのようなデータを収集して、どういった活用をするかはクライアントそれぞれで変更する必要があります。

新しいプライバシー要件が発表される前までは、App Store Connect にApp Privacyの情報を入力するのみでしたが、プライバシーマニフェストとしてビルド時に同じ情報を組み込む必要があります。

そのため、先ほどの図にApp Privacyのフローを入れると以下のようになりますが、ビルドBotはApp Privacyの情報は不要だったため参照していない状態になっています。

ビルドBotはApp Privacyのスプレッドシートの情報は参照していない

つまり、ビルド時にApp Privacyのスプレッドシートに記載されている情報を、ビルドBotが把握できるような仕組みが必要になります。

対応内容

新しく追加する必要がある部分を明確にしたのでここから対応内容を解説していきます。

ビルドBotが App Privacy の情報を参照し、プライバシーマニフェストの Privacy Nutrition Label Type を更新してビルドフェーズで組み込めるようにします。

App Privacyの情報を参照するために

対応前の運用で App Privacy の情報はスプレッドシートでアプリごとにシートを割り当てて管理しています。

ただ、ビルドBotで直接参照してしまうと以下のような懸念があります。

  • 変更するチームメンバーが多くビルド中に項目更新された場合に情報の不整合が発生しやすい
  • スプレッドシートでは変更履歴などを管理しづらい

そのため、AWS の S3 にスプレッドシートにある App Privacy の情報を JSON 形式に変換して、ビルド時にはその情報を元にプライバシーマニフェストを更新します。

プライバシーマニフェストは plist ファイルで、キー名はDescribing data use in privacy manifestsにあるNS〜で定義されています。

それを踏まえて S3 に配置するJSON形式は以下のようなフォーマットとしました 👇👇

{
  "updated": "2024-04-19 22:00:04",
  "app-privacy": [
    {
      "NSPrivacyCollectedDataType": "NSPrivacyCollectedDataTypePreciseLocation",
      "NSPrivacyCollectedDataTypeLinked": false,
      "NSPrivacyCollectedDataTypeTracking": false,
      "NSPrivacyCollectedDataTypePurposes": [
        "NSPrivacyCollectedDataTypePurposeAnalytics"
      ]
    },
    ・・・
    {
      "NSPrivacyCollectedDataType": "NSPrivacyCollectedDataTypeEnvironmentScanning",
      "NSPrivacyCollectedDataTypeLinked": false,
      "NSPrivacyCollectedDataTypeTracking": false,
      "NSPrivacyCollectedDataTypePurposes": [
        "NSPrivacyCollectedDataTypePurposeAnalytics",
        "NSPrivacyCollectedDataTypePurposeAppFunctionality"
      ]
    }
  ]
}

プライバシーマニフェストの情報とは別にupdatedも追加して、いつ更新されたものか把握できるようにしています。

実装

ビルド基盤の CI/CD には fastlane を使用しており、実装コードは Ruby となっているため、スプレッドシートを変換して S3 に配置する処理も Ruby で実装しています。 (これはいつか Swift にしていきたい💪)

スプレッドシートには App Privacy「データの種類」と対応する項目で定義されています。

App Privacy を管理しているスプレッドシートの一部

上記の項目とプライバシーマニフェストの Privacy Nutrition Label Types で利用されるキー値を対応させていきます。

# NSPrivacyCollectedDataTypePurposeのハッシュ
PURPOSE = {
    'サードパーティ広告' => 'NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising',
    'デベロッパの広告またはマーケティング' => 'NSPrivacyCollectedDataTypePurposeDeveloperAdvertising',
    'アナリティクス' => 'NSPrivacyCollectedDataTypePurposeAnalytics',
    '製品のパーソナライズ' => 'NSPrivacyCollectedDataTypePurposeProductPersonalization',
    'アプリの機能' => 'NSPrivacyCollectedDataTypePurposeAppFunctionality',
    'その他の目的' => 'NSPrivacyCollectedDataTypePurposeOther',
}

# NSPrivacyCollectedDataTypeのハッシュ
DATA_TYPE = {
    # 連絡先情報
    '名前' => 'NSPrivacyCollectedDataTypeName',
    'メールアドレス' => 'NSPrivacyCollectedDataTypeEmailAddress',
    '電話番号' => 'NSPrivacyCollectedDataTypePhoneNumber',
    '所在地' => 'NSPrivacyCollectedDataTypePhysicalAddress',
    'ユーザのその他の連絡先情報' => 'NSPrivacyCollectedDataTypeOtherUserContactInfo',
    # その他の要素も同様に定義
    # ・・・
}

次に App Privacy を表現するクラスを定義して、前述のハッシュを元にスプレッドシートの1行の情報を、JSON のキー名と対応した内容に変換できるようにしています。

スプレッドシートに入力されているため、タイポやフォーマットとして許容できない場合にはエラーとしてわかりやすくログが出せるようにエラーハンドリングしています。

# App Privacyを表現するクラス
class AppPrivacy
    attr_accessor :data_type, :linked, :tracking, :purposes

    def initialize(data_type, linked, tracking, purposes)
        @data_type = data_type
        @linked = linked
        @tracking = tracking
        @purposes = purposes
    end

    def self.from(data_type:, used:, linked:, tracking:, purposes:)
        decord_data_type = DATA_TYPE[data_type.strip]
        return nil if decord_data_type == nil || used != 'YES'
        decord_purposes = purposes
            .split('')
            .map do |key|
                puts key
                # 空文字でなく、かつ、PURPOSEに定義されていない用途がある場合はエラーを出力
                if !key.strip.empty? && PURPOSE[key.strip] == nil
                    raise "使用用途が見つかりませんでした: 「#{data_type.strip}」, #{key.strip} \n用途定義に改行やフォーマットに沿わないものがないか確認してください"
                end
                PURPOSE[key.strip] 
            end
        decord_purposes.compact!
        # 用途が定義されていない場合は許容しないためエラーを出力
        if decord_purposes.empty?
            raise "使用用途が定義されていません: 「#{data_type.strip}"
        end
        return AppPrivacy.new(
            decord_data_type,
            linked == 'YES',
            tracking == 'YES',
            decord_purposes
        )
    end
end

上記のクラスを利用してスプレッドシートから取得したデータをパースして JSON として保存して、S3 に配置するという流れです。 (残りの実装内容は割愛します)

CI/CD 側にここまでの実装を組み込むことで、クライアントが準備した App Privacy のスプレッドシート情報を、アプリごとに異なるプライバシーマニフェストの項目(Privacy Nutrition Label Types)として S3 から参照することができます。

あとは、ビルドBotで実行時に S3 にある JSON を元にプライバシーマニフェストを更新することで、アプリごとに異なる項目に対しても柔軟に対応することができるようになります 🎉🎉🎉

前に示した図が以下のようになり、ビルドBotで S3 の JSON 情報からプライバシーマニフェストを更新して App Store Connect に IPA をアップロードします。

緑色の矢印の部分がビルドBotに新しく追加されている

まとめ

ここまでの実装によって、ビルドBotで App Privacy の情報を参照してビルドすることができるようになりました。

これにより、アプリごとに異なるプライバシーマニフェストの項目を CI/CD でのビルド時に更新することができ、ひとつのアプリ情報に依存しない基盤を構築することができました。

前回の記事含めて、ようやくヤプリとして Apple の新しいプライバシー要件を全て満たしたことになります 🎉(長い道のりだった)

アプリプラットフォームとして、

  1. サードパーティSDKへの対応
  2. 本体プロジェクトへの対応
  3. ビルド基盤と運用体制への対応

という三つの枠組みで、しっかりと体制含めた対応をすることができました。

2024年5月1日より新しいプライバシー要件の運用が開始されてから、現時点(2024年6月)でストア審査でリジェクトが発生するなどのトラブルは発生していません。

800以上のアプリを提供するアプリプラットフォーマーとしてしっかりと対応ができたと思っています。

ヤプリで取り組んだ内容がどなたかのお役に立てれば嬉しいです。

P.S. 第3弾は Ruby 実装がタップリ現れるという記事内容となりました 😇 (Swiftの実装がない、、、、) ビルド基盤の詳細な部分では、プライバシーマニフェストの plist ファイル は Pure Swift で更新できるように実装されていたりします。 その辺りはまた別の機会でご紹介できればと思います。


注:可能な限り公式情報を参考に執筆しておりますが、記事内容に起因するトラブルなどの補償はしかねますのでご了承ください。


関連


  1. Privacy manifestファイル や、Privacy manifest という表現をされる場合もありますが、本記事ではプライバシーマニフェストとして統一しています。同じものを指しておりますので、その認識で読み進めていただければと思います 🤲