关于iphone:iOS唯一用户标识符

iOS unique user identifier

本问题已经有最佳答案,请猛点这里访问。

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


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

1
2
3
4
5
6
+ (NSString *)GetUUID {
  CFUUIDRef theUUID = CFUUIDCreate(NULL);
  CFStringRef string = CFUUIDCreateString(NULL, theUUID);
  CFRelease(theUUID);
  return [(NSString *)string autorelease];
}

然后将上述uuid设置为my nsstring:

1
NSString *UUID = [nameofclasswhereGetUUIDclassmethodresides UUID];

然后我使用sskeychain将该uuid存储到keychain

要使用sskeychain设置uuid,请执行以下操作:

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

检索它:

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

当您将uuid设置为keychain时,即使用户完全卸载应用程序,然后再次安装它,它也将保持不变。

以确保所有设备在密钥链中具有相同的UUID。

  • 设置应用程序以使用iCloud。
  • 将钥匙链中的UUID也保存为nsUserDefaults。
  • 将nsuserdefaults中的uuid传递给具有键值数据存储的云。
  • 在应用程序首次运行时,检查云数据是否可用,并在新设备的钥匙链中设置UUID。
  • 您现在有了一个唯一的标识符,它是持久的,并且与所有设备共享/同步。


    首先,UDID只在iOS 5中被弃用。这并不意味着它已经消失了。

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

    如果不需要udid,可以使用CFUUIDCreate()创建唯一的id,并在第一次启动时将其安全地保存为用户默认值(使用CFUUIDCreateString()首先将uuid转换为字符串)。它将在备份和恢复之后继续存在,甚至在原始用户切换到新设备时也会随之出现。在很多方面,UDID是更好的选择。

    如果你真的需要一个唯一的设备标识符(听起来不像你需要的),按照Suhail的回答中指出的MAC地址。


    我更新的应用程序只基于支持iOS 4.3及更高版本的唯一标识符。所以,

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

    2)我不能使用[UIDevice currentDevice].identifierForVendor.UUIDString,因为它仅在iOS 6.0及更高版本中可用,不能用于较低版本的iOS。

    3)Mac地址不是一个选项,因为在iOS-7中不允许使用它。

    4)openudid在一段时间前就被弃用了,iOS-6也有问题。

    5)iOS-5及以下版本也没有广告标识。

    最后这就是我所做的

    a)向项目中添加sfhfkeychainuils

    b)生成的cfuuid密钥字符串

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

    c)保存到钥匙链实用程序,否则每次都会生成一个新的唯一的

    最终代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    + (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;
    }

    enter image description here

    更多详细信息


    有些人想知道更多关于可用的不同选项的信息,如果知道,请从@nsquamber.java查看答案。如果你想知道如何使用nsuuid并与iCloud同步,请继续阅读。这篇文章的结尾比我原来想要的要冗长,但我希望它能让任何采取这些步骤的人都明白!

    使用NSUUID

    我使用nsuid类创建uuid:

    1
    NSUUID *uuid = [NSUUID UUID];

    然后,要创建字符串,只需调用UUIDString方法:

    1
    NSString *uuidString = [uuid UUIDString];

    或者用一行来做:

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

    imho,这比尝试使用cfuuidcreate并拥有必须维护的方法要容易得多。

    编辑:我现在使用uickeychainstore

    要使用uickeychainstore设置uuid,请执行以下操作:

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

    检索它:

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

    然后我使用sskeychain将该uuid存储到keychain

    要使用sskeychain设置uuid,请执行以下操作:

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

    检索它:

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

    当您将uuid设置为keychain时,即使用户完全卸载应用程序,然后再次安装它,它也将保持不变。

    与iCloud同步

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

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

    配置iCloud/nsubiQuitousKeyValueStore使用

  • 在Xcode中单击项目导航器顶部的项目。
  • 选择功能。
  • 打开iCloud。
  • 现在应该是这样的:Screenshot of iCloud enabled

    使用nsubiquitosKeyValueStore

    使用iCloud相当简单。写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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];

    阅读:

    1
    2
    3
    4
    5
    // create the key to store the ID
    NSString *userKey = @"com.sample.MyApp.user";

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

    在编写nsubiquitousKeyValueStore文档之前,必须先从iCloud中读取文档。要强制读取,请调用以下方法:

    1
    [[NSUbiquitousKeyValueStore defaultStore] synchronize];

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

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

    使用iCloud创建UUID

    结合nsuid、sskeychain和nsubiquitykeyValueStore,下面是生成用户ID的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    - (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中的任何数据,所以我将synchronize调用放在(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法的顶部。我也在那里添加了通知注册。这使我能够检测到iCloud的任何变化并适当地处理它们。

    这是一个示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    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的完整性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    - (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;
    }

    如果哪个乌伊德先来重要的话

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

    1
    2
    3
    4
    5
    6
    7
    // using dates
    NSDate *uuid1Timestamp = [NSDate dateWithTimeIntervalSince1970:timestamp1];
    NSDate *uuid2Timestamp = [NSDate dateWithTimeIntervalSince1970:timestamp2];
    NSTimeInterval timeDifference = [uuid1 timeIntervalSinceDate:uuid2Timestamp];

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


    在Github上有一个很好的替代方法,它基于MAC地址和运行良好的包标识符的组合生成唯一标识符:uidevice-with-unique identifier-for-ios-5


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

    • 如果在用户解锁设备之前访问该值,则该值可能为零。
    • 当用户从设备中删除该供应商的所有应用程序,然后重新安装其中一个或多个应用程序时,该值将更改。
    • 当使用Xcode安装测试版本或在使用特殊分发的设备上安装应用程序时,该值也会更改。

    如果出于广告目的需要标识符,请使用AsIdentifierManager的AdvertisingIdentifier属性。但是,请注意,上面讨论的第一点同样适用于此。

    来源:https://developer.apple.com/library/ios/documentation/uikit/reference/uidevice-class/reference/uidevice.html//apple-ref/occ/instp/uidevice/identifierforvendor


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

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

    我认为最好的办法是让用户填写Web上的数据,让PHP动态创建一个令牌,它不会存储在数据库中,而是发送给用户。然后,用户可以在目标设备上输入令牌并检索相关的XML。

    那就是我解决这个问题的办法。但不知道如何实现整个"创建唯一令牌"的功能。