Как получить действительно фиксированный Device-ID в swift?

Я использую приведенный ниже код с давних времен. Я думал, что это уникально

Но я удалил свое приложение и переустановил его, я получаю новый другой Device-ID!!!

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

}

при каждой новой переустановке я получаю новый ID!

Я хочу что-то, что остается прежним.

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 < 10. В Xcode 10+ CommonCrypto включен, так что вы можете пропустить этот шаг)

#import <CommonCrypto/CommonCrypto.h>

Если ваше приложение не имеет связующего заголовка, добавьте его в свой проект и обязательно установите его в настройках сборки:

Скриншот

После настройки вы можете сгенерировать свой собственный uuid, например так:

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

Запуск приложения и регистрация обоих выходных данных генерирует uuids что-то вроде этого:

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

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

Уникальный идентификатор в iPhone - это UDID, который недоступен в текущей версии ОС, поскольку его можно использовать не по назначению. Поэтому Apple предоставила другой вариант уникального ключа, но он меняется каждый раз, когда вы устанавливаете приложение. - Не может получить доступ к UDID

Но есть и другой способ реализовать эту функцию.

Сначала вы должны сгенерировать уникальный идентификатор:

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

    let swiftString: String = cfStr as String
    return swiftString
}

После получения этого, который является уникальным, но меняется после установки и переустановки приложения. Сохраните этот идентификатор в 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)
}

Затем, когда вам необходимо выполнить какое-либо задание на основе уникального идентификатора, сначала проверьте, сохранены ли какие-либо данные в цепочке для ключей по ключу "уникальный идентификатор". Даже если вы удалите приложение, данные цепочки ключей сохранятся, они будут удалены операционной системой.

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

Таким образом, вы можете сгенерировать уникальный идентификатор один раз и использовать этот идентификатор каждый раз.

ПРИМЕЧАНИЕ. Но при загрузке следующей версии приложения загрузите ее с тем же профилем обеспечения, в противном случае вы не сможете получить доступ к хранилищу ключей последней установленной программы. Key-Chain Store связан с профилем Provisioning.

Проверьте здесь

Доступ к уникальному идентификатору устройства (UDID) уже давно запрещен. identifierForVendor это его замена, и его поведение всегда было задокументировано.

Другие вопросы по тегам