【问题标题】:How to get the realy fixed Device-ID in swift?如何快速获得真正固定的设备 ID?
【发布时间】:2019-01-09 12:23:59
【问题描述】:

我使用下面的代码很长时间了。我认为它是独一无二的

但我已经删除了我的应用并重新安装了它,我得到了新的不同设备 ID。

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

每次重新安装,我都会获得一个新 ID。

我怎样才能得到保持不变的东西?

【问题讨论】:

  • 不要使用设备ID特定的任务,而是选择像用户ID+设备ID(标识符ForVendor)这样可以用作处理任何场景的关键,在推送通知相关的工作中,我宁愿选择这种方式。否则,如果您想要唯一的设备 ID,那么您可以使用第三方库,它会为您提供唯一 ID,否则您需要编写自己的逻辑来生成唯一 ID

标签: swift deviceid


【解决方案1】:

多年来一直禁止访问唯一设备 ID (UDID)。 identifierForVendor 是它的替代品,它的行为一直被记录在案。

【讨论】:

    【解决方案2】:

    由于从identifierForVendor 返回的值可以在删除应用程序时清除,或者如果用户在设置应用程序中重置它,则可以重置,因此您必须自己管理持久化它。

    有几种方法可以做到这一点。您可以设置一个分配 uuid 的服务器,然后通过用户登录持久化和获取服务器端,或者您可以在本地创建并将其存储在钥匙串中。

    删除应用时不会删除钥匙串中存储的项目。这允许您检查一个 uuid 之前是否已存储,如果是,您可以检索它,如果没有,您可以生成一个新的 uuid 并将其持久化。

    这是一种可以在本地进行的方法:

    /// 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
    }
    

    这是用于从钥匙串中存储/​​检索的类:

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

    然后你可以有一个用户类来获取标识符:

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

    如果您将它放在 viewDidLoad 中的测试应用程序中,启动该应用程序并记下控制台中打印的 uuid,删除该应用程序并重新启动,您将拥有相同的 uuid。

    如果您愿意,您还可以通过执行以下操作在应用中创建自己的完全自定义 uuid:

    // 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
        }
    }
    

    请注意,要使用 MD5 函数,您必须将以下导入添加到应用程序中的 Objective-C 桥接头:(如果您使用 Xcode )

    #import <CommonCrypto/CommonCrypto.h>
    

    如果您的应用没有桥接头,请将其添加到您的项目并确保在构建设置中进行设置:

    设置完成后,您可以像这样生成自己的自定义 uuid:

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

    运行应用程序并记录两个输出会生成类似这样的 uuid:

    // uuid from first example
    UUID: Optional("8A2496F0-EFD0-4723-8C6D-8E18431A49D2")
    
    // uuid from second custom example
    Other: Optional("63674d91f08ec3aaa710f3448dd87818")
    

    【讨论】:

    • 卸载时钥匙串数据是否还保留?根据forums.developer.apple.com/message/210531#210531,这在 iOS 10.3 中发生了变化
    • @Gereon 在 iOS 11.4 的 iPhone X 模拟器中进行测试,看起来它仍然有效。也在我运行 11.4 的设备上工作。
    • 谢谢,这真的很有趣!
    • 旧的设备 ID 已保存在我服务器的数据库中。因此,如果用户尝试登录,php 代码会比较第一个注册的设备 ID 和当前设备 ID。所以如果不一样,用户会收到消息“这不是注册的设备”。在这种情况下该怎么办?
    • 这更像是一个主观的业务逻辑问题,完全取决于你想做什么。如果用户尝试使用多个设备登录,您可能会遇到这种情况,这并不一定意味着他们已经删除了该应用程序。我可能会让他们选择更新注册设备并用新设备替换旧设备。如果您想锁定它,那么您可以让他们单击您发送到您系统上他们注册的电子邮件地址的电子邮件中的链接,该地址使用深度链接并启动应用程序以将更新的 uuid 发送到您的服务器。跨度>
    【解决方案3】:

    iPhone 中的唯一 ID 是 UDID,在当前版本的操作系统中无法访问,因为它可能会被滥用。因此,Apple 为唯一密钥提供了另一种选择,但每次安装应用程序时它都会改变。 --Cannot access UDID

    但还有另一种方法可以实现此功能。

    首先您必须生成唯一 ID:

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

    在获得唯一但在应用安装和重新安装后会发生变化的 this 之后。 将此 id 保存到 Key-Chain 上的任何键上,比如说“uniqueID”。

    将密钥保存在 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)
    }
    

    接下来,当您需要根据 uniqueID 执行任何任务时,首先检查是否有任何数据保存在 Key-Chain 中的 key "uniqueID" 上。 即使您卸载了应用程序,钥匙串数据仍然存在,它会被操作系统删除。

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

    这样就可以生成一次uniqueID,每次都使用这个id。

    注意:但是当您上传应用程序的下一个版本时,请使用相同的配置文件上传,否则您将无法访问上次安装的应用程序的钥匙串商店。 Key-Chain Store 与 Provisioning 配置文件相关联。

    Check Here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多