Post

iOS UUID Explained|Swift Techniques for Unique Device Identification

Discover how iOS developers solve device identification challenges using UUID in Swift, ensuring reliable app performance and user tracking across iOS versions.

iOS UUID Explained|Swift Techniques for Unique Device Identification

点击这里查看本文章简体中文版本。

點擊這裡查看本文章正體中文版本。

This post was translated with AI assistance — let me know if anything sounds off!


All About iOS UUID (Swift/iOS ≥ 6)

iPlayground 2018 Returns & The Story of UUIDs

Preface:

Last Saturday and Sunday, I attended the iPlayground Apple software developer conference. A colleague passed the event information to me, and I wasn’t familiar with it before going.

After two days, the entire event and schedule ran smoothly. The agenda included:

  1. Fun: Bicycles, Withering Code, iOS/API Evolution, Where’s Wally (CoreML Vision)

  2. Practical: Testing types (XCUITest, dependency injection), alternatives to SpriteKit for animation effects, GraphQL

  3. Kung Fu: In-Depth Analysis of Swift, iOS Jailbreak/Tweak Development, Redux

Bicycle Project was impressive, using an iPhone as a sensor to detect pedal rotation. The presenter rode the bike on stage to switch slides (the main goal was to create an open-source version of Zwift, and they also shared many pitfalls, such as Client/Server communication, latency issues, and magnetic interference).

Withering Dirty Code; it resonates deeply and brings a knowing smile; technical debt accumulates this way—rushed development leads to quick but poor architecture, and successors have no time to refactor, so it piles up more and more; in the end, sometimes the only option is to abandon that path entirely.

Test Classes (Design Patterns in XCUITest) KKBOX Seniors openly share their methods, code examples, common pitfalls, and solutions without holding anything back. This session is one of the most helpful for our work. Testing is an area I have always wanted to improve, so I can go back and study it thoroughly.

I really wanted to go on stage and share during the Lighting Talk session while listening from the audience 😂 Next time, I need to prepare earlier!

The official party after the event offered generous drinks, food, and venue. Listening to the honest stories from senior colleagues was not only relaxed and fun but also a great way to gain valuable workplace soft skills.

NTU Backend Cafe

NTU Backend Cafe

I just found out this is the first edition. I’m truly honored to participate. Thanks to all the staff and speakers for their hard work!

The purpose of attending a seminar is essentially to: broaden your horizons by absorbing new knowledge, understanding the ecosystem, and encountering topics you don’t usually engage with, and deepen your expertise by reviewing familiar topics to see if you’ve missed anything or discovered alternative approaches.

I took many notes to review and study at my own pace later.

All About UUIDs

Because I immediately applied it to the app after listening; this class was taught by senior Zonble. When I heard about development from iPhone OS 2 to iOS 12, I was amazed. Since I started late in the industry, I began coding from iOS 11/Swift 4, so I didn’t experience the chaotic period when Apple changed APIs.

It’s quite reasonable that UUIDs can be blocked; if used properly, they help identify user devices, support advertising, or enable third parties to perform unique ad targeting. However, if a company has malicious intent, they can use this mechanism to backtrack and learn about the phone owner. For example, if you have travel apps, Taipei bus apps, BMW apps, and baby care apps installed, they can infer that you often travel abroad, have children, and live in Taipei. Combined with personal data you enter in apps, the potential misuse is unimaginable.

But this also affected many legitimate users. For example, those who used UUID as the data decryption key for users or as a device identifier were greatly impacted. I really admire the engineers from that time—they had to quickly come up with alternative solutions while dealing with furious bosses and users.

Alternative solutions:

This article focuses on obtaining the UUID to identify a device uniquely. If you want alternatives to find out which apps a user has installed, you can refer to the following keyword search methods: UIPasteboard pasteboardWithName: create: (using the clipboard to share data between apps), canOpenURL:, info.plist LSApplicationQueriesSchemes (using canOpenURL to check if an app is installed, must be listed in info.plist, up to 50 entries).

  1. Using MAC Address as UUID, but later got banned

  2. Finger Printing (Canvas/User-Agent…): Not researched, but mainly used to generate the same UUID for Safari and the app, used for Deferred Deep Linking
    AmIUnique?

  3. IDentifier For Vendor (IDFV): The current mainstream solution 🏆
    The concept is that Apple generates a UUID for the user based on the Bundle ID prefix. Apps with the same Bundle ID prefix will get the same UUID on the same device. For example: com.518.work / com.518.job will receive the same UUID on the same device.
    As the original term “ID For Vendor” suggests, Apple treats apps with the same prefix as from the same vendor, so sharing the UUID is allowed.

IDentifier For Vendor (IDFV):

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

Note: When all apps from the same vendor are removed and then reinstalled, a new UUID will be generated ( if both com.518.work and com.518.job are deleted, reinstalling com.518.work will generate a new UUID )
Similarly, if you have only one app, deleting and reinstalling it will generate a new UUID

Because of this feature, our company’s other apps use Key-Chain to solve this problem. After listening to the senior speaker’s advice, we confirmed that this approach is correct!

Process as follows:

When the Key-Chain UUID field has a value, use it; otherwise, use the IDFA UUID value and write it back

When the Key-Chain UUID field has a value, use it; otherwise, take the IDFA UUID value and write it back.

Key-Chain Writing Method:

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) // Delete existing item
    SecItemAdd(query as CFDictionary, nil) // Add new item
}

Key-Chain Reading Method:

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
} 

If you find Key-Chain operations too cumbersome, you can encapsulate them yourself or use third-party libraries.

Complete CODE:

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
    }
}()

Because I need to reference it in other Extension Targets as well, I directly wrapped it as a closure parameter for use.

If you have any questions or suggestions, feel free to contact me.


Buy me a beer

This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.

Improve this page on Github.

This post is licensed under CC BY 4.0 by the author.