卸载设备上的ios应用后,如何在ios中保留identifierForVendor?


72

我正在开发一个iOS应用程序,该应用程序调用Web服务进行登录,当时我将登录凭据与供应商标识符(identifierForVendor)发送到Web服务器,以唯一地标识用于这些凭据的设备,因此用户只能拥有一个设备和一个凭据。

我得到了identifierForVendor

NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString

然后,该标识符将存储在Web服务器的数据库以及设备数据库中。下次用户打开应用程序并尝试从Web服务器下载数据时,首先将用户设备上的本地identifierForVendor与Web服务器上存储的标识符进行比较。

用户卸载应用程序并重新安装它时出现问题,我发现identifierForVendor已更改。因此用户无法继续进行。

我阅读了苹果文档UIDevice文档

如此处所述,如果来自同一供应商的所有应用都从设备上卸载,那么在新安装该供应商的任何应用时,它将采用新的identifierForVendor。

那么我该如何处理呢?


2
我不知道是否可以,但是如何将其保存在钥匙串中呢?您在启动时检查此标识符是否在KeyChain中,如果不是,则得到一个并将其存储在Keychain中。
Larme 2014年

2
嗨,Gekb,您发现查询的任何解决方案了吗?即使我也面临着同样的情况。
Kirti Nikam

Answers:


66

您可以将其保存在KeyChain中

-(NSString *)getUniqueDeviceIdentifierAsString
{

 NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];

 NSString *strApplicationUUID = [SSKeychain passwordForService:appName account:@"incoding"];
 if (strApplicationUUID == nil)
 {
    strApplicationUUID  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    [SSKeychain setPassword:strApplicationUUID forService:appName account:@"incoding"];
 }

 return strApplicationUUID;
}

10
如果启用了iCloud KeyChain同步,是否可以使用?
Kristof Van Landschoot 2014年

有趣的问题。不知道
nerowolfe

7
同步KeyChain时,此功能将不起作用,所有同步的设备将获得相同的商户ID。
rckoenes 2015年

7
由于上面提到的同步问题,我将对此予以否决。
乔尼

1
尽管要当心钥匙串,但钥匙串中的项目目前在应用程序的卸载-安装周期中仍然有效,但将来可能会发生变化。在iOS 10.3 beta 3中,钥匙串项已删除,但在最终版本中已更改。有关更多信息,请参见stackoverflow.com/questions/18911434/…
安德烈亚斯·鲍尔森

19

通常,不要使用identifierForVendor。而是使用NSUUID生成自定义UUID并将其存储在钥匙串中(因为如果删除并重新安装该应用程序,则不会删除钥匙串)。


如果在不同设备上使用同一应用程序的不同版本,identifierForVendor是否有可能返回重复项?
PrasadW

我希望是的,但机会无限小
2015年

1
没有重复的机会。UUID不是随机的。它们是经过计算的,UUID的一部分是设备ID。
Marcus Adams

4
“由NSUUID创建的UUID符合RFC 4122版本4,并且使用随机字节创建。” -但是,尽管如此,重复的机会还是很小的,正如其他人所说的那样:“只有在接下来的100年中每秒产生10亿个UUID的情况下,重复创建一次的可能性才约为50%。” - stackoverflow.com/a/1155027/859027
mcfedr

最好使用identifierForVendor,但这不应再存储在钥匙串中。stackoverflow.com/a/48405739/62921这不是持久的,因为iOS的10.3
ForceMagic

10

除了@nerowolfe的答案

SSKeychain使用kSecAttrSynchronizableAny默认的同步模式。您可能不希望identifierForVendor跨多个设备同步,因此这是一个代码:

// save identifierForVendor in keychain without sync
NSError *error = nil;
SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
query.service = @"your_service";
query.account = @"your_account";
query.password = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
query.synchronizationMode = SSKeychainQuerySynchronizationModeNo;
[query save:&error];

1
来自Cocoapods:SSKeychain已弃用,转而支持SAMKeychain
爬网程序

5

您可以尝试使用KeyChain保存您的VendorIdentifier,即使您卸载应用程序,它也将一直存在直到重置设备为止。


不幸的是,他们最近更改了它,KeyChain不再安全地存储持久数据。stackoverflow.com/a/48405739/62921
ForceMagic '18

@ForceMagic感谢您的更新,我的回答是3年了,将会找到另一种方法,这个问题的答案仍然存储在Keychain中,但它们没有更新,毫无意义,因为它在被问到问题时就可以使用了。
iphonic

是的,我确实了解几乎所有答案都是古老的。我看到下降投票更像是“这不再是真的”,而不是“这是一个错误的答案”。但是也许我错了。
ForceMagic

@ForceMagic答案仍然是正确的,请参阅此stackoverflow.com/a/18944600/790842和此stackoverflow.com/a/43063683/790842
iphonic

从您的链接:At Apple Documentation It is suggested that this is about to change and we should NOT rely on keychain access data being intact after an app uninstallation
ForceMagic

4

好。我不想使用第三方-即SSKeychain。因此,这是我尝试过的代码,非常简单并且运行良好:

    NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:bundleId accessGroup:nil];
if(![keychainItem objectForKey:(__bridge id)(kSecValueData)]){
    NSString *idfa = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    [keychainItem setObject:idfa forKey:(__bridge id)(kSecValueData)];
    NSLog(@"saving item %@", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}else{
    NSLog(@"saved item is %@", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}

+1这是一个非常简单的解决方案,结合了iCloud钥匙串同步(使用属性kSecAttrSynchronizable),是解决此问题的最佳解决方案。
loretoparisi

1
@loretoparisi(如果使用kSecAttrSynchronizable)会不会在所有设备上都具有相同的值?
jcesarmobile

完全是@jcesarmobile,如果您添加kSecAttrSynchronizable到@ gautam-jain代码,则会在iCloud应用程序属性中获取它并同步到所有设备。
loretoparisi

那么如果您希望每个设备都具有不同的价值就不是一个好主意
jcesarmobile

@jcesarmobile是,根据您的需求,您可以保留设备的标识符,设备供应商的属性identifierForVendor advertisingIdentifierfrom ASIdentifierManager
loretoparisi 2015年

4

迅捷版

func UUID() -> String {

    let bundleName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
    let accountName = "incoding"

    var applicationUUID = SAMKeychain.passwordForService(bundleName, account: accountName)

    if applicationUUID == nil {

        applicationUUID = UIDevice.currentDevice().identifierForVendor!.UUIDString

        // Save applicationUUID in keychain without synchronization
        let query = SAMKeychainQuery()
        query.service = bundleName
        query.account = accountName
        query.password = applicationUUID
        query.synchronizationMode = SAMKeychainQuerySynchronizationMode.No

        do {
            try query.save()
        } catch let error as NSError {
            print("SAMKeychainQuery Exception: \(error)")
        }
    }

    return applicationUUID
}

2
我不会将其称为Swift版本,因为它需要第三方对象
blkbam

2

再也没有确定的方法将唯一编号链接到设备,这是Apple隐私准则所不允许的。

您可以尝试在钥匙串中保存自己的唯一ID,但是如果用户清除了自己的设备,该ID也将消失。

通常,将设备链接到用户是不对的,因为您不再标识用户而是设备。因此,您只需要更改您的API,以便用户可以重新登录并将供应商ID绑定到用户帐户。

当用户拥有一部以上的设备(例如iPhone和iPad)并在两者上使用您的应用程序时,还会发生什么?由于您的身份验证基于唯一的ID,因此无法完成。


@DouglasHeld我明白这一点,但是Apple做到了这一点,我试图解释为什么不应该这样做。
rckoenes 2015年

@rckoenes如果需要硬件绑定,则需要标识设备。您还如何阻止用户将其安装在多个设备上?一个人付款一次,然后所有他的朋友也可以使用他的登录凭据免费获得!!对于开发人员而言似乎并不合理。
2015年

2
@ash我听到了,但是Apple不允许这样做。每个Apple帐户支持5种设备。限制这样做违反了Apple政策。
rckoenes 2015年

1

我曾使用KeychainAccess吊舱解决此问题。

在您的pod文件中:

pod 'KeychainAccess', '~> 2.4' //If you are using Swift 2.3 
pod 'KeychainAccess' //Defaults to 3.0.1 which is in Swift 3

KeychainAccess模块导入文件中要在钥匙串中设置UUID的位置

import KeychainAccess

使用以下代码设置并从钥匙串获取UUID:

注意: BundleId是键,UUID是值

var bundleID = NSBundle.mainBundle().bundleIdentifier
    var uuidValue = UIDevice.currentDevice().identifierForVendor!.UUIDString

 //MARK: - setVenderId and getVenderId
    func setVenderId() {

        let keychain = Keychain(service: bundleID!)

        do {
            try keychain.set(venderId as String, key: bundleID!)
            print("venderId set : key \(bundleID) and value: \(venderId)")
        }
        catch let error {
            print("Could not save data in Keychain : \(error)")
        }
    }

    func getVenderId() -> String {
        let keychain = Keychain(service: bundleID!)
        let token : String = try! keychain.get(bundleID!)!
        return token
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.