【问题标题】:iOS unique user identifier [duplicate]iOS唯一用户标识符[重复]
【发布时间】:2011-11-08 12:52:59
【问题描述】:

我正在为 iphone 编写一个应用程序,它使用 REST 与我的服务器通信。主要问题是,我需要以某种方式识别用户。不久前,我们被允许使用 UDID,但现在它不再被允许了。那么我应该改用什么?我需要 iphone 上的某种标识符,所以用户将删除应用程序,重新安装它,他会得到相同的 id。

【问题讨论】:

  • 这是一个非常热门的话题。您是否首先在这里搜索过类似的问题?谷歌呢?非常热门的话题...
  • 有人说我们应该在这种情况下使用 mac-address,但我不确定 Apple 规则是否允许。
  • Apple 不允许这样做,因为它与 udid 一样因设备而异。
  • 仅供参考,关于这个主题的谷歌搜索会导致“这个”线程。

标签: iphone objective-c ios


【解决方案1】:

我用CFUUIDCreate()创建了一个UUID:

+ (NSString *)GetUUID {
  CFUUIDRef theUUID = CFUUIDCreate(NULL);
  CFStringRef string = CFUUIDCreateString(NULL, theUUID);
  CFRelease(theUUID);
  return [(NSString *)string autorelease];
}

然后将上面的 UUID 设置为我的 NSString:

NSString *UUID = [nameofclasswhereGetUUIDclassmethodresides UUID];

然后我使用 SSKeyChain 将该 UUID 存储到钥匙串中

使用 SSKeyChain 设置 UUID:

[SSKeychain setPassword:UUID forService:@"com.yourapp.yourcompany" account:@"user"];

检索它:

NSString *retrieveuuid = [SSKeychain passwordForService:@"com.yourapp.yourcompany" account:@"user"];

当您将 UUID 设置为 Keychain 时,即使用户完全卸载应用程序然后再次安装它也会保留。

确保所有设备在钥匙串中都具有相同的 UUID。

  1. 设置您的应用程序以使用 iCloud。
  2. 将钥匙串中的 UUID 也保存到 NSUserDefaults。
  3. 通过键值对数据存储将 NSUserDefaults 中的 UUID 传递到云端。
  4. App 首次运行时,检查云数据是否可用,并在新设备的 Keychain 中设置 UUID。

您现在拥有一个唯一标识符,该标识符是持久的并与所有设备共享/同步。

【讨论】:

  • +1 这是我见过的每个用户唯一 uuid 的最佳方法,而不仅仅是设备!非常感谢。
  • 我不太明白,你能帮我更好地理解它吗?有人愿意与我实时聊天以便我理解这一点吗?我将不胜感激
  • 非常好的和简单的解决方案。奇迹般有效。谢谢
  • 比起奶酪,我更喜欢它。谢谢!
  • 您好,提交到appstore有什么问题吗?用户也可以清除钥匙串吗?
【解决方案2】:

首先,在 iOS 5 中,UDID 是 only deprecated。这并不意味着它已经消失(现在)。

其次,你应该问自己是否真的需要这样的东西。如果用户得到一个新设备并在上面安装你的应用程序怎么办?同一用户,但 UDID 已更改。同时,原来的用户可能已经卖掉了他的旧设备,所以现在一个全新的用户安装了您的应用,而您根据 UDID 认为这是一个不同的人。

如果您不需要 UDID,请使用 CFUUIDCreate() 创建唯一 ID 并在首次启动时将其保存到用户默认值(使用 CFUUIDCreateString() 先将 UUID 转换为字符串)。它将在备份和恢复中幸存下来,甚至在原始用户切换到新设备时也会出现。它在很多方面都比 UDID 更好。

如果您确实需要一个唯一的 设备 标识符(听起来不像您),请按照 Suhail 的回答中指出的那样选择 MAC 地址。

【讨论】:

  • CFUUIDCreate - 如果同一用户,将重新安装我的应用程序,它会保持不变,还是会改变?
  • @Drabuna:不,CFUUIDCreate() 会在你每次调用它时创建一个新的 UUID。
  • 用户默认设置被备份,并且从一个安装到下一个安装仍然存在,不是吗?
  • @Tommy:是的。如果 UUID 被保存到备份并从那里恢复,它会继续存在。
  • 一个更好的策略,特别是如果你试图识别 user 而不是 device 是使用 run CFUUIDCreate() 一次然后存储生成NSUbiquitousKeyValueStore,以便它在具有相同 iCloud 帐户的所有 iOS 设备上同步,并在所有重新安装/设备重置时保留,无论用户是否使用备份。
【解决方案3】:

我正在更新我的应用程序,该应用程序仅基于支持 iOS 4.3 及更高版本的唯一标识符运行。所以,

1) 我无法使用[UIDevice currentDevice].uniqueIdentifier;,因为它不再可用

2) 我无法使用[UIDevice currentDevice].identifierForVendor.UUIDString,因为它仅适用于 iOS 6.0 及更高版本,并且无法用于较低的 iOS 版本。

3) mac 地址不是一个选项,因为它在 iOS-7 中是不允许的

4) OpenUDID 之前是 deprecated,并且在 iOS-6 上也有问题。

5) 广告标识符也不适用于 iOS-5 及以下版本

我终于做到了

a) 将SFHFKeychainUtils 添加到项目中

b) 生成的 CFUUID 密钥字符串

 CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
    udidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));

c) 将其保存到 Key Chain Utils 否则每次都会生成一个新的唯一性

最终代码

+ (NSString *)GetDeviceID {
    NSString *udidString;
   udidString = [self objectForKey:@"deviceID"];
    if(!udidString)
    {
    CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
    udidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
    CFRelease(cfuuid);
        [self setObject:udidString forKey:@"deviceID"];
    }
    return udidString;
}

+(void) setObject:(NSString*) object forKey:(NSString*) key
{
    NSString *objectString = object;
    NSError *error = nil;
    [SFHFKeychainUtils storeUsername:key
                         andPassword:objectString
                      forServiceName:@"LIB"
                      updateExisting:YES
                               error:&error];

    if(error)
        NSLog(@"%@", [error localizedDescription]);
}

+(NSString*) objectForKey:(NSString*) key
{
    NSError *error = nil;
    NSString *object = [SFHFKeychainUtils getPasswordForUsername:key
                                                  andServiceName:@"LIB"
                                                           error:&error];
    if(error)
        NSLog(@"%@", [error localizedDescription]);

    return object;
}

For further Details

【讨论】:

  • 这个答案值得更多支持
  • 很好解释。当然这应该是公认的答案
  • @OrlandoLeite 和 user2043155。谢谢..我期待这应该被认为是正确的答案。 :'(
  • @NSQuamber.java 在被问到这个问题的时候,已经选择的答案是完全可以接受的,你不应该期望 OP 仅仅因为你放弃了更多而改变他们选择的答案迄今为止的答案,但我相信这个答案至少值得 +1,因为它是一个很好解释的答案。
【解决方案4】:

有些人想了解更多关于可用的不同选项的信息,如果您想了解,请查看来自@NSQuamber.java 的答案。如果您想知道如何使用 NSUUID 并与 iCloud 同步,请继续阅读。这篇文章最终比我最初想要的更冗长,但我希望它能让任何采取这些步骤的人都清楚!

使用 NSUUID

我使用 NSUUID 类来创建 UUID:

NSUUID *uuid = [NSUUID UUID];

那么创建字符串,只需要调用UUIDString方法:

NSString *uuidString = [uuid UUIDString];

或一行完成:

NSString *uuidString = [[NSUUID UUID] UUIDString];

恕我直言,这比尝试使用 CFUUIDCreate 并拥有一个您必须维护的方法要容易得多。


编辑:我现在使用UICKeyChainStore

使用 UICKeyChainStore 设置 UUID:

UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:@"com.sample.MyApp"];
keychain[@"com.sample.MyApp.user"] = userID;

要检索它:

UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:@"com.sample.MyApp"];
NSString *userID = keychain[@"com.sample.MyApp.user"];

然后我使用 SSKeyChain 将该 UUID 存储到钥匙串中

使用 SSKeyChain 设置 UUID:

[SSKeychain setPassword:userID forService:@"com.sample.MyApp.user" account:@"com.sample.MyApp"];

要检索它:

NSString *userID = [SSKeychain passwordForService:@"com.sample.MyApp.user" account:@"com.sample.MyApp"];

当您将 UUID 设置为 Keychain 时,即使用户完全卸载应用程序然后再次安装它也会保留。

与 iCloud 同步

因此,确保所有用户的设备使用相同的 UUID 很有用。这是为了确保数据在所有设备之间同步,而不是每个设备都认为自己是唯一的用户。

cmets 中有几个问题是关于同步如何工作的答案,所以现在我已经完成了所有工作,我将提供更多详细信息。

配置 iCloud/NSUbiquitousKeyValueStore 使用

  1. 在 Xcode 的 Project Navigator 顶部单击您的项目。
  2. 选择能力
  3. 打开 iCloud。

它现在应该看起来像这样:

使用 NSUbiquitousKeyValueStore

使用 iCloud 相当简单。写:

// create the UUID
NSUUID *userUUID = [[NSUUID UUID];
// convert to string
NSString *userID = [userUUID UUIDString];
// create the key to store the ID
NSString *userKey = @"com.sample.MyApp.user";

// Save to iCloud
[[NSUbiquitousKeyValueStore defaultStore] setString:userID forKey:userKey];

阅读:

// create the key to store the ID
NSString *userKey = @"com.sample.MyApp.user";

// read from iCloud
NSString *userID = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:userKey];

在您编写NSUbiquitousKeyValueStore documentation 声明之前,您需要先从 iCloud 读取。要强制读取,请调用以下方法:

[[NSUbiquitousKeyValueStore defaultStore] synchronize];

要让您的应用接收 iCloud 更改通知,请添加以下通知:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(iCloudStoreDidChange:)
                                             name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
                                           object:[NSUbiquitousKeyValueStore defaultStore]];

使用 iCloud 创建 UUID

结合 NSUUID、SSKeychain 和 NSUbiquityKeyValueStore,这是我生成用户 ID 的方法:

- (NSUUID *)createUserID {
    NSString *userKey = @"com.sample.MyApp.user";
    NSString *KEYCHAIN_ACCOUNT_IDENTIFIER = @"com.sample.MyApp";
    NSString *userID = [SSKeychain passwordForService:userKey account:KEYCHAIN_ACCOUNT_IDENTIFIER];
    if (userID) {
        return [[NSUUID UUID] initWithUUIDString:userID];
    }

    // check iCloud
    userID = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:userKey];
    if (!userID) {
        // none in iCloud, create one
        NSUUID *newUUID = [NSUUID UUID];
        userID = [newUUID UUIDString];
        // save to iCloud
        [[NSUbiquitousKeyValueStore defaultStore] setString:userID forKey:userKey];
    }

    // store the user ID locally
    [SSKeychain setPassword:userID forService:userKey account:KEYCHAIN_ACCOUNT_IDENTIFIER];
    return [[NSUUID UUID] initWithUUIDString:userID];
}

如何确保您的用户 ID 是同步的

因为写入 iCloud 需要先下载 iCloud 中的所有数据,所以我将同步调用放在 (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法的顶部。我还在那里添加了通知注册。这使我能够检测到来自 iCloud 的任何更改并适当地处理它们。

这是一个示例:

NSString *const USER_KEY = @"com.sample.MyApp.user";
NSString *const KEYCHAIN_ACCOUNT_IDENTIFIER = @"com.sample.MyApp";

- (void)iCloudStoreDidChange:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    NSNumber *changeReason = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey];
    NSArray *keysChanged = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey];
    if (changeReason) {
        switch ([changeReason intValue]) {
            default:
            case NSUbiquitousKeyValueStoreServerChange:
            case NSUbiquitousKeyValueStoreInitialSyncChange:
                // check changed keys
                for (NSString *keyChanged in keysChanged) {
                    NSString *iCloudID = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:keyChanged];
                    if (![keyChanged isEqualToString:USER_KEY]) {
                        NSLog(@"Unknown key changed [%@:%@]", keyChanged, iCloudID);
                        continue;
                    }

                    // get the local key
                    NSString *localID = [SSKeychain passwordForService:keyChanged account:KEYCHAIN_ACCOUNT_IDENTIFIER];
                    if (!iCloudID) {
                        // no value from iCloud
                        continue;
                    }
                    // local ID not created yet
                    if (!localID) {
                        // save the iCloud value locally
                        [SSKeychain setPassword:iCloudID forService:keyChanged account:KEYCHAIN_ACCOUNT_IDENTIFIER];
                        continue; // continue because there is no user information on the server, so no migration
                    }

                    if ([iCloudID isEqualToString:localID]) {
                        // IDs match, so continue
                        continue;
                    }

                    [self handleMigration:keyChanged from:localID to:iCloudID];
                }

                break;
            case NSUbiquitousKeyValueStoreAccountChange:
                // need to delete all data and download new data from server
                break;
        }
    }
}

当应用程序启动或返回前台时,我会强制与 iCloud 同步并验证 UUID 的完整性。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self configureSecKeyWrapper];
    // synchronize data from iCloud first. If the User ID already exists, then we can initialize with it
    [[NSUbiquitousKeyValueStore defaultStore] synchronize];
    [self checkUseriCloudSync];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // synchronize changes from iCloud
    [[NSUbiquitousKeyValueStore defaultStore] synchronize];
    [self checkUseriCloudSync];
}

- (BOOL)checkUseriCloudSync {
    NSString *userKey = @"com.sample.MyApp.user";
    NSString *KEYCHAIN_ACCOUNT_IDENTIFIER = @"com.sample.MyApp";
    NSString *localID = [SSKeychain passwordForService:userKey account:KEYCHAIN_ACCOUNT_IDENTIFIER];
    NSString *iCloudID = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:userKey];

    if (!iCloudID) {
        // iCloud does not have the key saved, so we write the key to iCloud
        [[NSUbiquitousKeyValueStore defaultStore] setString:localID forKey:userKey];
        return YES;
    }

    if (!localID || [iCloudID isEqualToString:localID]) {
        return YES;
    }

    // both IDs exist, so we keep the one from iCloud since the functionality requires synchronization
    // before setting, so that means that it was the earliest one
    [self handleMigration:userKey from:localID to:iCloudID];
    return NO;
}

如果哪个 UUID 先出现很重要

在我的 UserID 用例中,我假设 iCloud 中的值是要保留的值,因为它将是第一个推送到 iCloud 的 UUID,无论哪个设备首先生成了 UUID。你们中的大多数人可能会走同样的路,因为你不会真正关心它解析为哪个 UUID,只要它解析为单个 UUID。对于那些真正关心哪个在先的人,我建议您同时存储 UUID 和时间戳生成 ([[NSDate date] timeIntervalSince1970]),以便您可以查看哪个更旧:

// using dates
NSDate *uuid1Timestamp = [NSDate dateWithTimeIntervalSince1970:timestamp1];
NSDate *uuid2Timestamp = [NSDate dateWithTimeIntervalSince1970:timestamp2];
NSTimeInterval timeDifference = [uuid1 timeIntervalSinceDate:uuid2Timestamp];

// or just subtract
double timeDifference = timestamp1 - timestamp2;

【讨论】:

  • 您如何处理尚未发生 iCloud 同步的情况?因此,您会因为同步延迟而为设备创建新的 UUID?您是否在生产环境中运行此程序,是否遇到过有关同步延迟的任何问题?
  • @user1218464:我对我的答案做了很大的修改,以展示我如何处理同步。没有问题,因为我将数据迁移到本地正确的用户,并向我的服务器发出请求以执行相同的操作。
  • 感谢您的更新。这很有帮助。我还没有进行足够的测试,但在您回复之前,我尝试了一种稍微不同的方法将钥匙串同步到 iCloud,这样您就不需要存储在 NSUbiquitousKeyValueStore 中。您还可以获得创建钥匙串密码的时间戳,以确保您始终使用最旧的密码。我不确定这是否是最佳选择,但它肯定会减少代码。
  • @user1218464:你最后用了什么? NSUbiquitousKeyValueStore 是在 iCloud 中存储键值对的方式,所以我猜你使用了文档机制?
  • 如果您在添加钥匙串项目时设置了 kSecAttrSynchronizable 属性,它会从 iOS 7.0.3 开始将钥匙串项目与 iCloud 同步。因此,我只是设置了该属性,并且钥匙串项 (UUID) 使用 iCloud 跨设备同步。
【解决方案5】:

Github 上有一个不错的替代方案,它根据 Mac 地址和捆绑标识符的组合生成唯一标识符,效果很好:UIDevice-with-UniqueIdentifier-for-iOS-5

【讨论】:

  • 感谢 Patel Ji,我想这是更好的选择,因为在某些情况下,我们希望特定应用始终为该设备发出相同的 UIID。
  • MAC 地址不具备使其成为 UDID 的合适替代品所需的唯一性属性。在这里查看我的答案:stackoverflow.com/a/16230057/112191
  • MAC 地址现在在 iOS 7 中不可用。
【解决方案6】:

在 iOS7 中,Apple 在 UIDevice 类中引入了一个名为“identifierForVendor”的只读属性。如果你决定使用它,你应该注意以下几点,

  • 如果在用户解锁设备之前访问该值,则该值可能为 nil
  • 当用户从设备中删除该供应商的所有应用并随后重新安装其中一个或多个应用时,该值会发生变化。
  • 使用 Xcode 安装测试版本或使用 ad-hoc 分发在设备上安装应用时,该值也可能发生变化。

如果您需要用于广告目的的标识符,请使用 ASIdentifierManager 的 advertisingIdentifier 属性。但是请注意,上面讨论的第一点也适用于此。

来源:https://developer.apple.com/library/ios/documentation/uikit/reference/UIDevice_Class/Reference/UIDevice.html#//apple_ref/occ/instp/UIDevice/identifierForVendor

【讨论】:

    【解决方案7】:

    这确实是一个热门话题。我有一个必须迁移的应用程序,因为它使用 UDID 来命名要存储在服务器上的 XML 文件。然后带有应用程序的设备将连接到服务器并下载其特定的 udid.xml 并对其进行解析以工作。

    我一直在想,如果用户移动到新设备,应用程序就会崩溃。所以我真的应该用别的东西。问题是,我不使用数据库来存储数据。数据只是以 xml 格式存储,每个设备一个 xml 文件存储在云端。

    我认为最好的办法是让用户填写网络上的数据,让 php 动态创建一个令牌,该令牌不会存储在数据库中,而是发送给用户。然后,用户可以在目标设备上输入令牌并检索有问题的 xml。

    这将是我解决问题的方法。不过,不确定如何实现整个“创建唯一令牌”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      • 2013-07-05
      相关资源
      最近更新 更多