How to get the realy fixed Device-ID in swift?

青春壹個敷衍的年華 提交于 2019-12-11 06:29:17

问题


I use the below code since long time. I have thought it is unique

But I have deleted my app and reinstalled it, I get new different Device-ID.

if let uuid = UIDevice.current.identifierForVendor?.uuidString {
  print(uuid)
}

every new reinstall, I get a new ID.

How do I get something which stays the same?


回答1:


Since the value returned from identifierForVendor can be cleared when deleting the app or reset if the user resets it in the Settings app, you have to manage persisting it yourself.

There are a few ways to accomplish this. You can setup a server that assigns a uuid which is then persisted and fetched server side via user login, or you can create and store it locally in the keychain.

Items stored in the keychain will not be deleted when the app is deleted. This allows you to check if a uuid was previously stored, if so you can retrieve it, if not you can generate a new uuid and persist it.

Here's a way you could do it locally:

/// Creates a new unique user identifier or retrieves the last one created
func getUUID() -> String? {

    // create a keychain helper instance
    let keychain = KeychainAccess()

    // this is the key we'll use to store the uuid in the keychain
    let uuidKey = "com.myorg.myappid.unique_uuid"

    // check if we already have a uuid stored, if so return it
    if let uuid = try? keychain.queryKeychainData(itemKey: uuidKey), uuid != nil {
        return uuid
    }

    // generate a new id
    guard let newId = UIDevice.current.identifierForVendor?.uuidString else {
        return nil
    }

    // store new identifier in keychain
    try? keychain.addKeychainData(itemKey: uuidKey, itemValue: newId)

    // return new id
    return newId
}

And here's the class for storing/retrieving from the keychain:

import Foundation

class KeychainAccess {

    func addKeychainData(itemKey: String, itemValue: String) throws {
        guard let valueData = itemValue.data(using: .utf8) else {
            print("Keychain: Unable to store data, invalid input - key: \(itemKey), value: \(itemValue)")
            return
        }

        //delete old value if stored first
        do {
            try deleteKeychainData(itemKey: itemKey)
        } catch {
            print("Keychain: nothing to delete...")
        }

        let queryAdd: [String: AnyObject] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: itemKey as AnyObject,
            kSecValueData as String: valueData as AnyObject,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
        ]
        let resultCode: OSStatus = SecItemAdd(queryAdd as CFDictionary, nil)

        if resultCode != 0 {
            print("Keychain: value not added - Error: \(resultCode)")
        } else {
            print("Keychain: value added successfully")
        }
    }

    func deleteKeychainData(itemKey: String) throws {
        let queryDelete: [String: AnyObject] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: itemKey as AnyObject
        ]

        let resultCodeDelete = SecItemDelete(queryDelete as CFDictionary)

        if resultCodeDelete != 0 {
            print("Keychain: unable to delete from keychain: \(resultCodeDelete)")
        } else {
            print("Keychain: successfully deleted item")
        }
    }

    func queryKeychainData (itemKey: String) throws -> String? {
        let queryLoad: [String: AnyObject] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: itemKey as AnyObject,
            kSecReturnData as String: kCFBooleanTrue,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        var result: AnyObject?
        let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
            SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
        }

        if resultCodeLoad != 0 {
            print("Keychain: unable to load data - \(resultCodeLoad)")
            return nil
        }

        guard let resultVal = result as? NSData, let keyValue = NSString(data: resultVal as Data, encoding: String.Encoding.utf8.rawValue) as String? else {
            print("Keychain: error parsing keychain result - \(resultCodeLoad)")
            return nil
        }
        return keyValue
    }
}

Then you can just have a user class where you get the identifier:

let uuid = getUUID()
print("UUID: \(uuid)") 

If you put this in a test app in viewDidLoad, launch the app and note the uuid printed in the console, delete the app and relaunch and you'll have the same uuid.

You can also create your own completely custom uuid in the app if you like by doing something like this:

// convenience extension for creating an MD5 hash from a string
extension String {

    func MD5() -> Data? {
        guard let messageData = data(using: .utf8) else { return nil }

        var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
        _ = digestData.withUnsafeMutableBytes { digestBytes in
            messageData.withUnsafeBytes { messageBytes in
                CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes)
            }
        }

        return digestData
    }
}

// extension on UUID to generate your own custom UUID
extension UUID {

    static func custom() -> String? {
        guard let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String else {
            return nil
        }

        let unique = bundleID + NSUUID().uuidString
        let hashData = unique.MD5()
        let md5String = hashData?.map { String(format: "%02hhx", $0) }.joined()

        return md5String
    }
}

Note that to use the MD5 function you will have to add the following import to an Objective-C bridging header in your app: (if you're building with Xcode < 10. In Xcode 10+ CommonCrypto is included so you can skip this step)

#import <CommonCrypto/CommonCrypto.h>

If your app does not have a bridging header, add one to your project and make sure to set it in build settings:

Once it's setup you can generate your own custom uuid like this:

let otherUuid = UUID.custom()
print("Other: \(otherUuid)")

Running the app and logging both outputs generates uuids something like this:

// uuid from first example
UUID: Optional("8A2496F0-EFD0-4723-8C6D-8E18431A49D2")

// uuid from second custom example
Other: Optional("63674d91f08ec3aaa710f3448dd87818")



回答2:


Access to the unique device id (UDID) has been disallowed for ages now. identifierForVendor is its replacement, and its behaviour has always been documented.




回答3:


Unique Id in iPhone is UDID, which is not accessible in current version of OS, because it can be misused. So Apple has given the other option for the unique key but it change every time you install the app. --Cannot access UDID

But there is another way to implement this feature.

First you have to generate the Unique ID :

func createUniqueID() -> String {
    let uuid: CFUUID = CFUUIDCreate(nil)
    let cfStr: CFString = CFUUIDCreateString(nil, uuid)

    let swiftString: String = cfStr as String
    return swiftString
}

After getting the this which is unique but changes after the app install and reinstall. Save this id to the Key-Chain on any key let say "uniqueID".

To save the key in keyChain :

   func getDataFromKeyChainFunction() {
        let uniqueID = KeyChain.createUniqueID()
        let data = uniqueID.data(using: String.Encoding.utf8)
        let status = KeyChain.save(key: "uniqueID", data: data!)
        if let udid = KeyChain.load(key: "uniqueID") {
            let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
            print(uniqueID!)
        }
    }

func save(key: String, data: Data) -> OSStatus {
        let query = [
            kSecClass as String       : kSecClassGenericPassword as String,
            kSecAttrAccount as String : key,
            kSecValueData as String   : data ] as [String : Any]    
        SecItemDelete(query as CFDictionary)    
        return SecItemAdd(query as CFDictionary, nil)
}

Next when you required to perform any task based on uniqueID ,first check if any data is saved in Key-Chain on key "uniqueID" . Even if you uninstall the app , the key-chain data is still persisted, it will delete by OS.

func checkUniqueID() {
    if let udid = KeyChain.load(key: "uniqueID") {
        let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
        print(uniqueID!)
    } else {
        let uniqueID = KeyChain.createUniqueID()
        let data = uniqueID.data(using: String.Encoding.utf8)
        let status = KeyChain.save(key: "uniqueID", data: data!)
        print("status: ", status)
    }
}

In this way you can generate the uniqueID once and use this id every time.

NOTE: But when you upload next version of your app, upload it with the same Provisioning Profile otherwise you cannot access the key-chain store of your last installed app. Key-Chain Store is associated with the Provisioning profile.

Check Here



来源:https://stackoverflow.com/questions/51641614/how-to-get-the-realy-fixed-device-id-in-swift

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!