iOS唯一用户标识符

时间:2011-09-01 15:57:50

标签: iphone objective-c ios

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

7 个答案:

答案 0 :(得分:186)

我使用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存储到Keychain中

使用SSKeyChain设置UUID:

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

要检索它:

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

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

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

  1. 设置您的应用以使用iCloud。
  2. 也将Keychain中的UUID保存到NSUserDefaults。
  3. 将NSUserDefaults中的UUID传递给具有键值数据存储的云。
  4. 在应用程序首次运行时,检查云数据是否可用,并在新设备的Keychain中设置UUID。
  5. 您现在拥有一个唯一标识符,该标识符是持久的,并与所有设备共享/同步。

答案 1 :(得分:69)

首先,iOS 5中的UDID为only deprecated。这并不意味着它已经消失了。

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

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

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

答案 2 :(得分:38)

我正在更新我的应用程序,该应用程序仅基于支持 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;
}

enter image description here

For further Details

答案 3 :(得分:15)

有些人想了解更多有关不同选项的信息,如果你这样做,请查看@ 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存储到Keychain中

使用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时,即使用户完全卸载App然后再次安装它,它仍会保留。

与iCloud同步

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

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

配置iCloud / NSUbiquitousKeyValueStore使用

  1. 点击Xcode中 Project Navigator 顶部的项目。
  2. 选择功能
  3. 开启iCloud。
  4. 它应该看起来像这样: Screenshot of iCloud enabled

    使用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];
    

    在您可以编写首先需要从iCloud中读取的NSUbiquitousKeyValueStore documentation状态之前。要强制读取,请调用以下方法:

    [[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和时间戳生成([[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;
    

答案 4 :(得分:10)

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

答案 5 :(得分:1)

在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

答案 6 :(得分:0)

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

我一直在想,如果用户移动到新设备,应用程序将会中断。所以我真的应该用别的东西。问题是,我没有使用数据库来获取数据。数据只是以xml格式存储,每个设备存储在云上一个xml文件。

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

这将是我解决问题的方法。不确定如何实施整体创造独特的令牌'但是事情。