我正在编写一个与我的服务器通过REST通信的iPhone应用程序。主要问题是,我需要以某种方式识别用户。不久前,我们可以使用UDID,但现在它已经不再允许使用了。那么我应该使用什么替代品呢?我需要一种iPhone上的标识符,这样用户就可以删除应用程序,再次安装它,他将获得相同的ID。
我正在编写一个与我的服务器通过REST通信的iPhone应用程序。主要问题是,我需要以某种方式识别用户。不久前,我们可以使用UDID,但现在它已经不再允许使用了。那么我应该使用什么替代品呢?我需要一种iPhone上的标识符,这样用户就可以删除应用程序,再次安装它,他将获得相同的ID。
我使用 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时,即使用户完全卸载并重新安装应用程序,它也会保持不变。
确保所有设备都在Keychain中具有相同的UUID。
现在您拥有一个持久且与所有设备共享/同步的唯一标识符。
CFUUIDCreate()
创建唯一标识符,并在第一次启动时将其保存到用户默认设置中(先使用CFUUIDCreateString()
将UUID转换为字符串)。它将在备份和还原中存活,甚至在原始用户切换到新设备时也会带来。在许多方面,它比UDID更好。CFUUIDCreate()
每次调用都会创建一个新的UUID。 - Ole BegemannCFUUIDCreate()
一次,然后将结果存储在NSUbiquitousKeyValueStore
中,以便它在所有具有相同iCloud帐户的iOS设备上同步,并在所有重新安装/设备重置时保留,无论用户是否使用了备份。 - Garrett我正在更新我的应用程序,基于唯一标识符,支持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) 将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;
}
有些人想了解更多可用选项,如果你想了解,请查看@NSQuamber.java的答案。如果您想知道如何使用NSUUID并与iCloud同步,请继续阅读。这篇文章比我原来想象的要冗长,但我希望它能为那些正在执行这些步骤的人提供清晰的指导!
我使用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时,即使用户完全卸载应用程序,然后重新安装,它也将持久存在。
因此,确保所有用户设备使用相同的UUID非常有用。这是为了确保数据在所有设备上同步,而不是每个设备都认为它是唯一的用户。
我的回答中有几个评论中关于如何进行同步的问题,所以现在我已经全部操作成功,我会提供更多细节。
现在它应该看起来像这样:
使用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文档之前,必须先从iCloud读取。要强制读取,请调用以下方法:
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
若要使您的应用程序接收iCloud中更改通知,请添加以下通知:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(iCloudStoreDidChange:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:[NSUbiquitousKeyValueStore defaultStore]];
结合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];
}
由于向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;
}
在我的用户ID使用场景中,我认为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;
handleMigration
方法的实现? - ipatchhandleMigration
方法仅是为了将我的数据从一个UUID迁移到另一个UUID。实质上,我正在更改所有具有user
字段的CoreData
实体的所有权到新的UUID。然后,我将删除旧用户实体。然后将所有更改的对象推送到服务器以在服务器端进行修改。 - mikeho在Github上有一个很好的替代方案,它基于Mac地址和Bundle标识生成唯一标识符,这个方案非常有效:UIDevice-with-UniqueIdentifier-for-iOS-5
如果您需要广告标识符,请使用ASIdentifierManager的advertisingIdentifier属性。但是请注意,上述第一点仍然适用。
。这确实是一个热门话题。我有一个应用程序需要迁移,因为它使用UDID来命名要存储在服务器上的XML文件。然后,带有该应用程序的设备将连接到服务器并下载其特定的udid.xml并解析它以使其工作。
我一直在思考,如果用户更换新设备,应用程序将会出现问题。所以我真的应该使用其他东西。问题是,我不使用数据库来存储数据。数据只是以xml格式存储,每个设备一个xml文件存储在云端。
我认为最好的方法是让用户在网页上填写数据,让php即时创建一个令牌,该令牌不会存储在数据库中,而是发送给用户。然后用户可以在目标设备上输入令牌并检索相关的xml。
这将是我解决问题的方法。不确定如何实现整个“创建唯一令牌”的过程。