記事

iOS UUIDの基礎と応用|SwiftでUUID管理を最適化する方法

iOS開発者向けにUUIDの生成・管理の課題を解決。SwiftでのUUID活用法を解説し、識別子管理の効率化とセキュリティ向上を実現します。

iOS UUIDの基礎と応用|SwiftでUUID管理を最適化する方法

本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。

記事一覧


iOS UUID に関するあれこれ (Swift/iOS ≥ 6)

iPlayground 2018 から帰ってきました & UUIDについて

はじめに:

先週の土日、同僚から紹介された iPlayground Apple ソフトウェア開発者向けセミナーに参加してきました。参加前はこのイベントについてよく知りませんでした。

二日間にわたり、全体のイベントとスケジュールはスムーズに進行し、議題内容は以下の通りです:

  1. 趣味:自転車、枯れたコード、iOS/APIの進化史、ウィリーはどこ?(CoreML Vision)

  2. 実用的:テスト類(XCUITest、依存性注入)、SpriteKitによるアニメーション効果の代替案、GraphQL

  3. 真の技術:Swift、iOS 脱獄/Tweak開発、Reduxの徹底解析

自転車プロジェクトが印象的でした。iPhoneをセンサーとして使い、自転車のペダルの回転を感知し、ステージ上で自転車に乗りながらスライドを切り替える仕組みです(先輩の主な目標はオープンソース版Zwiftの開発で、クライアント/サーバー通信、遅延問題、磁場干渉など多くの落とし穴も共有してくれました)。

腐敗したDirty Code;聞くと胸が痛み、心の中で苦笑いする;技術的負債はこうして積み重なっていく。開発スケジュールが厳しいため、構造の悪い手早い方法を使い、後から引き継ぐ人もリファクタリングする時間がなく、どんどん増えていく;最終的には本当にこの道を断つしかなくなるかもしれない。

測試クラス(Design Patterns in XCUITest) KKBOXの先輩 が、彼らの方法やコード例の詳細、直面した問題と解決策を完全に公開してくれました。このセッションは私たちの仕事に最も役立つ内容の一つです。テスト分野はずっと強化したいと思っていた部分なので、しっかりと研究したいと思います。

Lighting Talkの部分は客席で聞いていてもぜひ自分も登壇して共有したいと思いました😂 次回は早めに準備をしておこうと思います!

会後の公式パーティーは、飲み物や食べ物、会場すべてに心がこもっており、先輩方の本音を聞くことができ、とてもリラックスして楽しいだけでなく、多くの職場でのソフトスキルも学べました。

台大後台カフェ

台大バックエンドカフェ

これは初回の開催だと知り、本当に参加できて光栄でした。スタッフの皆さんと講演者の方々、お疲れ様でした!

セミナーに参加する目的は大きく分けて、知識の幅を広げること、新しい情報を吸収し、生態系を理解し、普段触れない分野に触れること、そして知識の深さを増すことです。すでに自分が経験している分野については、見落としがないかや他の方法がないかを確認するために参加します。

たくさんメモを取ったので、ゆっくり復習して味わいたいと思います。

UUIDのあれこれ

なぜなら、聴講後すぐに実際のアプリに応用したからです。この講義はZonble先輩が担当され、iPhone OS 2からiOS 12までの話を聞いて感服しました。私は業界に入ったのが遅く、iOS 11/Swift 4から開発を始めたため、AppleのAPI変更による混乱の時代を経験していません。

UUIDが取得可能からブロックされるようになったのは、ある意味合理的です。良い目的で使われる場合は、ユーザーのデバイス識別や広告、第三者によるユニーク性を活かした広告運用などですが、悪意のある業者がこの仕組みを使って逆に調べれば、そのスマホの持ち主がどんな人か分かってしまいます。(例えば、旅行アプリ+台北のバスアプリ+BMWアプリ+育児アプリを入れていれば、よく海外に行き、子供がいて台北に住んでいると推測できるなどの情報です)さらに、アプリに入力した個人情報と組み合わせれば、何に使われるか想像もつきません。

しかし、これにより多くの正当なユーザーにも影響が及びました。例えば、UUIDをユーザーのデータ復号キーとして使ったり、UUIDでデバイス判定をしていた場合、大きな影響を受けました。当時のエンジニアの先輩方には本当に感服します。こうした影響で上司やユーザーから激しく叱責される中、機転を利かせて代替手段を見つけなければなりませんでした。

代替案:

本記事はUUIDを取得してデバイスの一意識別子を識別することを主題としています。もしユーザーがどのアプリをインストールしているかを知る代替方法を探している場合は、以下のキーワード検索方法を参考にしてください: UIPasteboard pasteboardWithName: create:(クリップボードを使ってアプリ間で共有) 、canOpenURL: info.plist LSApplicationQueriesSchemes(canOpenURLでアプリのインストール有無を確認、info.plistに最大50件まで列挙が必要)

  1. MAC AddressをUUIDとして使用していたが、その後禁止された

  2. Finger Printing (Canvas/User-Agent…) :詳しくは調べていませんが、この方法は主にSafariとアプリで同じUUIDを生成するために使われます。 Deferred Deep Linking(遅延ディープリンク)に利用
    AmIUnique?

  3. ID entifier F or V endor (IDFV):現在の主流な解決策🏆
    概念としては、AppleがBundle IDのプレフィックスに基づいてユーザーのUUIDを生成します。同じBundle IDプレフィックスは同じUUIDを生成します。例えば、com.518.work と com.518.job は同じデバイスで同じUUIDが得られます。
    原文のID For Vendorと同様に、同じプレフィックスはAppleにより同じベンダーのアプリとみなされるため、UUIDの共有が許可されています。

ID entifier F or V endor (IDFV):

1
let DEVICE_UUID:String = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString

注意すべき点:同じベンダーのすべてのAPPを削除してから再インストールすると、新しいUUIDが生成されます( com.518.workとcom.518.jobの両方を削除し、再度com.518.workをインストールすると新しいUUIDが生成されます
同様に、APPが1つだけの場合は、削除して再インストールすると新しいUUIDが生成されます

この特性のため、弊社の他のアプリではKey-Chainを使ってこの問題を解決しています。講師の先輩の指摘を聞いて、この方法が正しいことも確認できました!

流れは以下の通りです:

Key-ChainにUUIDの値がある場合は取得し、ない場合はIDFAのUUID値を取得して書き戻す

Key-ChainのUUIDフィールドに値がある場合は取得し、ない場合はIDFAのUUID値を取得して書き戻す

Key-Chain 書き込み方法:

1
2
3
4
5
6
7
8
9
if let data = DEVICE_UUID.data(using: .utf8) {
    let query = [
        kSecClass as String       : kSecClassGenericPassword as String,
        kSecAttrAccount as String : "DEVICE_UUID",
        kSecValueData as String   : data ] as [String : Any]
    
    SecItemDelete(query as CFDictionary) // 既存のアイテムを削除
    SecItemAdd(query as CFDictionary, nil) // 新しいアイテムを追加
}

Key-Chain の読み取り方法:

1
2
3
4
5
6
7
8
9
10
11
let query = [
    kSecClass as String       : kSecClassGenericPassword,
    kSecAttrAccount as String : "DEVICE_UUID",
    kSecReturnData as String  : kCFBooleanTrue,
    kSecMatchLimit as String  : kSecMatchLimitOne ] as [String : Any]

var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr,let dataTypeRef = dataTypeRef as? Data,let uuid = String(data:dataTypeRef, encoding: .utf8) {
   //uuid
} 

もしKey-Chainの操作が面倒なら、自分でラップするかサードパーティのライブラリを使うこともできます。

完全なコード:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let DEVICE_UUID:String = {
    let query = [
        kSecClass as String       : kSecClassGenericPassword,
        kSecAttrAccount as String : "DEVICE_UUID",
        kSecReturnData as String  : kCFBooleanTrue,
        kSecMatchLimit as String  : kSecMatchLimitOne ] as [String : Any]
    
    var dataTypeRef: AnyObject? = nil
    let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
    if status == noErr,let dataTypeRef = dataTypeRef as? Data,let uuid = String(data:dataTypeRef, encoding: .utf8) {
        return uuid
    } else {
        let DEVICE_UUID:String = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
        if let data = DEVICE_UUID.data(using: .utf8) {
            let query = [
                kSecClass as String       : kSecClassGenericPassword as String,
                kSecAttrAccount as String : "DEVICE_UUID",
                kSecValueData as String   : data ] as [String : Any]
        
            SecItemDelete(query as CFDictionary) // 既存のアイテムを削除
            SecItemAdd(query as CFDictionary, nil) // 新しいアイテムを追加
        }
        return DEVICE_UUID
    }
}()

他のExtension Targetでも参照が必要なため、直接クロージャーパラメータとしてまとめて使用しています。

ご質問やご意見がありましたら、こちらからご連絡ください

PostZMediumToMarkdown によって Medium から変換されました。


🍺 Buy me a beer on PayPal

👉👉👉 Follow Me On Medium! (1,053+ Followers) 👈👈👈

本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

Improve this page on Github.

本記事は著者により CC BY 4.0 に基づき公開されています。