Uniquely identifying an iOS user
The correct solution is to use the iCloud Key-Value Store, where you can store a unique user ID without requiring any kind of authentication or user information such as an email address.
The end result is a random UUID (nothing that actually IDENTIFIES the user), that is different for each user, but will persist across multiple devices registered to the same iCloud account.
We define a field in our iCloud KV Store, let's call it userID. When the app is launched, we first check the userID. If it's there, then we're all set with our user's unique ID. If not, then this is the first time we're running for this user. We generate a random UUID and store it in the KV Store under userID. That's all there is to it.
Our experience shows that this UUID is unique per iTunes account. If your end-users are using family sharing, those accounts will be assigned different UUIDs (which may or may not be desirable but there's nothing you can do about it). Any number of devices launching under the same iTunes account will see the same UUID.
This approach is totally legit and should be approved by Apple with no issues.
Obviously you must enable iCloud Key-Value store on Xcode under Capabilities, just turn on the iCloud Switch.
Here's a simple class that implements this concept in Objective-C:
@implementation EEUserID
+ (NSUUID *) getUUID
{
NSUUID *uuid = nil;
NSString *uuidString = [[NSUbiquitousKeyValueStore defaultStore] stringForKey: @"EEUserID"];
if (uuidString == nil)
{
// This is our first launch for this iTunes account, so we generate random UUID and store it in iCloud:
uuid = [NSUUID UUID];
[[NSUbiquitousKeyValueStore defaultStore] setString: uuid.UUIDString forKey: @"EEUserID"];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
}
else
{
uuid = [[NSUUID alloc] initWithUUIDString: uuidString];
}
return uuid;
}
+ (NSString *) getUUIDString
{
NSUUID *uuid = [self getUUID];
if (uuid != nil)
return uuid.UUIDString;
else
return nil;
}
+ (void) load
{
// get changes that might have happened while this
// instance of your app wasn't running
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
}
@end
And for the header file:
#import <Foundation/Foundation.h>
@interface EEUserID : NSObject
+ (NSUUID *) getUUID;
+ (NSString *) getUUIDString;
@end
To use, all you have to do is invoke:
NSString *uniqueIDForiTunesAccount = [EEUserID getUUIDString];
Enjoy.
Generate a UUID with this:
NSString *UUID() {
CFUUIDRef cfuuid = CFUUIDCreate(NULL);
NSString *uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
CFRelease(cfuuid);
return uuid;
}
Nothing here is deprecated or frowned on by Apple -- in fact, it is the way they suggest you do it. Store the generated UUID in the keychain and it will be there -- even if the user uninstalls and reinstalls your app. The UUID is unique for the device and the time it was generated.
You can then use various schemes to have the user group their devices together -- iCloud, or some sort of key that you deliver from the server.
Good luck!
Addition:
Here's how I store it in the keychain, using the uuid as a username and generating a random password:
uuid = UUID();
[keychainItemWrapper setObject:uuid forKey:(__bridge_transfer id)kSecAttrAccount];
NSString *pass_token = randomString(10);
[keychainItemWrapper setObject:pass_token forKey:(__bridge_transfer id)kSecValueData];
Note that all of this can be done without any input from the user.
Update:
MCSMKeychainItem has a great solution to UUID generation and storage with [MCSMApplicationUUIDKeychainItem applicationUUID]
. The library also has [MCSMGenericKeychainItem genericKeychainItemWithService:service username:username password:password]
. Together, these functions take care of everything mentioned above. Easy to install with CocoaPods too.