Uniquely identify IOS device [duplicate] - iphone

I am developing an iOS app which calls web-service for login and at that time i send login credentials to web server along with vendor identifier (identifierForVendor),to identify device uniquely for those credentials.So user can have only one device and one credential.
I got identifierForVendor with
NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString
This identifier will then store in database of web server and also in device database.Next time when user opens application and will try to download data from web server firstly local identifierForVendor on users device will compare with identifier stored on web server.
Problem occurs when user uninstall app and reinstall it, I found that identifierForVendor is changed. So user cannot proceed further.
I read apple documentation UIDevice Documentation
As mention there, if all app from same vendor uninstalls from device then at time of new installation of any app from that vendor will take new identifierForVendor.
So how to deal with this in my case ?

You may keep it in KeyChain
-(NSString *)getUniqueDeviceIdentifierAsString
{
NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
NSString *strApplicationUUID = [SSKeychain passwordForService:appName account:#"incoding"];
if (strApplicationUUID == nil)
{
strApplicationUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[SSKeychain setPassword:strApplicationUUID forService:appName account:#"incoding"];
}
return strApplicationUUID;
}

Generally, don't use identifierForVendor. Instead, use NSUUID to generate a custom UUID and store that in the keychain (because the keychain isn't deleted if the app is deleted and reinstalled).

Addition to #nerowolfe's answer.
SSKeychain uses kSecAttrSynchronizableAny as a default synchronization mode. You probably don't want identifierForVendor to be synced across multiple devices so here is a code:
// save identifierForVendor in keychain without sync
NSError *error = nil;
SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
query.service = #"your_service";
query.account = #"your_account";
query.password = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
query.synchronizationMode = SSKeychainQuerySynchronizationModeNo;
[query save:&error];

You can try use KeyChain to save your VendorIdentifier, that will exist till your device is reset, even if you uninstall your app.

Ok. I didn't want to use a third party - namely SSKeychain. So this is the code I tried, fairly simple and works well:
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:bundleId accessGroup:nil];
if(![keychainItem objectForKey:(__bridge id)(kSecValueData)]){
NSString *idfa = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[keychainItem setObject:idfa forKey:(__bridge id)(kSecValueData)];
NSLog(#"saving item %#", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}else{
NSLog(#"saved item is %#", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}

Swift version
func UUID() -> String {
let bundleName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
let accountName = "incoding"
var applicationUUID = SAMKeychain.passwordForService(bundleName, account: accountName)
if applicationUUID == nil {
applicationUUID = UIDevice.currentDevice().identifierForVendor!.UUIDString
// Save applicationUUID in keychain without synchronization
let query = SAMKeychainQuery()
query.service = bundleName
query.account = accountName
query.password = applicationUUID
query.synchronizationMode = SAMKeychainQuerySynchronizationMode.No
do {
try query.save()
} catch let error as NSError {
print("SAMKeychainQuery Exception: \(error)")
}
}
return applicationUUID
}

There is no definite way to link a unique number to a device any more, this is not allowed with the Apple privacy guidelines.
You can try to save your own Unique ID in the keychain, but if the user clear his device this ID is also gone.
Generally is it just wrong to link a device to a user, since you are not longer identifying users but devices. So you should just change your API so that the user can re-login and that the vendor ID is bound to the users account.
Also what happens when the user has more then one device, like an iPhone and iPad, and uses you app on both? Since you authentication is based an unique ID this can not be done.

I had used KeychainAccess pod for this problem.
In your pod file :
pod 'KeychainAccess', '~> 2.4' //If you are using Swift 2.3
pod 'KeychainAccess' //Defaults to 3.0.1 which is in Swift 3
Import KeychainAccess module in file where you want to set UUID in keychain
import KeychainAccess
Use below code to set and get UUID from keychain :
Note : BundleId is key and UUID is value
var bundleID = NSBundle.mainBundle().bundleIdentifier
var uuidValue = UIDevice.currentDevice().identifierForVendor!.UUIDString
//MARK: - setVenderId and getVenderId
func setVenderId() {
let keychain = Keychain(service: bundleID!)
do {
try keychain.set(venderId as String, key: bundleID!)
print("venderId set : key \(bundleID) and value: \(venderId)")
}
catch let error {
print("Could not save data in Keychain : \(error)")
}
}
func getVenderId() -> String {
let keychain = Keychain(service: bundleID!)
let token : String = try! keychain.get(bundleID!)!
return token
}

Related

I want unique identifier string which detect iPhone device (just like UDID)?

I want unique identifier string for iPhone devices instead of UDID and MAC.
1. Get UDID and MAC are deprecated by apple.
2. We can use UUID but it will get change after reinstalling app or delete app.
I want any unique value of device which is remain same after app reinstall OR delete OR upgrade iOS version.
What you can do is get a unique identifier using [[UIDevice currentDevice] identifierForVendor]or any other unique identifier generator. After that, you should store that value on keychain using KeychainItemWrapper and use. Once you store a value on the keychain it'll not remove even after you delete and reinstall the app.
Here is a guide for keychain access - Link
Try
[[UIDevice currentDevice] identifierForVendor];
It is from iOS 6.
As #danypata said, use the identifierForVendor as described in this answer.
Or alternatively you might be able to use the advertising NSUDID as described here.
However these can come back as nil or be changed over time. The user can opt out of advertiser tracking, so I don't recommend using it to track users for your own purposes.
I guess it depends on why you are tracking their device in the first place. My attitude is that I don't need to track users habits FOREVER. I only need to track general user trends and some DAU info. So I make up my own UDID - which will change on each install of the app. In my next version I will use the identifierForVendor and if it's NIL I will make up my own.
This is how I make my own:
// this makes a device id like: UUID = 89CD872F-C9AF-4518-9E6C-A01D35AF091C
// except that I'm going to attach some other attributes to it like the OS version, model type, etc.
// the UUID that is stored in user defaults will be like the one above.. but the device id that gets returned
// from this function and sent to [my online system] will have the extra info at the end of the string.
- (void) createUUID {
// for collecting some data.. let's add some info about the device to the uuid
NSString *thisDeviceID;
NSString *systemVersion = [[UIDevice currentDevice]systemVersion];
NSString *model = [[UIDevice currentDevice]model];
NSString *retinaTag;
if (retina) {
retinaTag = #"Retina";
}
else {
retinaTag = #"";
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id uuid = [defaults objectForKey:#"uniqueID"];
if (uuid)
thisDeviceID = (NSString *)uuid;
else {
CFStringRef cfUuid = CFUUIDCreateString(NULL, CFUUIDCreate(NULL));
thisDeviceID = (__bridge NSString *)cfUuid;
CFRelease(cfUuid);
[defaults setObject:thisDeviceID forKey:#"uniqueID"];
}
//NSLog(#"UUID = %#", thisDeviceID);
MYthisDeviceID = [NSString stringWithFormat:#"%#-%#-%#-%#",thisDeviceID,systemVersion,retinaTag,model];
//NSLog(#"UUID with info = %#", MYthisDeviceID);
}
Then the single string that gets sent to my server has both a UDID in it and specs about the device and os. Until the user completely deletes and reloads the app the stats show usage on that device. To not get double udids if they update to a new os you can crop to just the udid portion.
I don't use the mac address at all because it was my understanding that apple didn't want us to. Although I can't find any documentation that says it at the moment.
UPDATE for iOS7:
I now use this code which works under io6 and io7:
NSString *globalDeviceID;
- (void) createUUID
{
NSString *thisDeviceID;
NSString *systemVersion = [[UIDevice currentDevice]systemVersion];
NSString *model = [[UIDevice currentDevice]model];
NSString *retinaTag;
if (retina) {
retinaTag = #"Retina";
}
else {
retinaTag = #"";
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id uuid = [defaults objectForKey:#"deviceID"];
if (uuid)
thisDeviceID = (NSString *)uuid;
else {
if ([[UIDevice currentDevice] respondsToSelector:#selector(identifierForVendor)]) {
thisDeviceID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
}
else
{
CFStringRef cfUuid = CFUUIDCreateString(NULL, CFUUIDCreate(NULL));
thisDeviceID = (__bridge NSString *)cfUuid;
CFRelease(cfUuid);
}
[defaults setObject:thisDeviceID forKey:#"deviceID"];
}
NSLog(#"UUID = %#", thisDeviceID);
globalDeviceID = [NSString stringWithFormat:#"%#-%#-%#-%#",thisDeviceID,systemVersion,retinaTag,model];
NSLog(#"UUID with info = %#", globalDeviceID);
}
Or simply use the following and store to NSUserDefaults
[NSProcessInfo processInfo].globallyUniqueString
Global ID for the process. The ID includes the host name, process ID, and a time stamp, which ensures that the ID is unique for the network.
I'd suggest having a look at OpenUDID.
I believe underneath it uses the MAC address, so if you are correct in saying that accessing the MAC address is deprecated, then it probably won't be of much use, however, I think it would be unreasonable of Apple to remove access; the MAC address has other applications that just identifying the device for the purposes of UDID

Is there a way to retrieve the Bundle Seed ID of an app at runtime? [duplicate]

How can I access the Bundle Seed ID/Team ID/App Identifier Prefix string programmatically? (These are all the same thing as far as I can tell).
I am using the UICKeychainStore keychain wrapper to persist data across several applications. Each of these applications has a shared keychain access group in their entitlement plists, and share the same provisioning profile. By default, the keychain services use the first access group in the plist as the access group to save data to. This looks like "AS234SDG.com.myCompany.SpecificApp" when I debug UICKeychainStore. I would like to set the access group to "AS234SDG.com.myCompany.SharedStuff", but I can't seem to locate how to get the "AS234SDG" string of the access group programmatically, and would like to avoid hard-coding it if possible.
Info.plist can have your own information and if you write a value with $(AppIdentifierPrefix), it is replaced to the real app identifier prefix at building phase.
So, try this:
In your Info.plist, add an info about app identifier prefix.
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
You can then retrieve it programmatically with Objective-C:
NSString *appIdentifierPrefix =
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"AppIdentifierPrefix"];
and with Swift:
let appIdentifierPrefix =
Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String
Note that appIdentifierPrefix ends with a period; e.g. AS234SDG.
You can programmatically retrieve the Bundle Seed ID by looking at the access group attribute (i.e. kSecAttrAccessGroup) of an existing KeyChain item. In the code below, I look up for an existing KeyChain entry and create one if it doesn't not exist. Once I have a KeyChain entry, I extract the access group information from it and return the access group's first component separated by "." (period) as the Bundle Seed ID.
+ (NSString *)bundleSeedID {
NSString *tempAccountName = #"bundleSeedID";
NSDictionary *query = #{
(__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount : tempAccountName,
(__bridge NSString *)kSecAttrService : #"",
(__bridge NSString *)kSecReturnAttributes: (__bridge NSNumber *)kCFBooleanTrue,
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess) {
return nil;
}
status = SecItemDelete((__bridge CFDictionaryRef)query); // remove temp item
NSDictionary *dict = (__bridge_transfer NSDictionary *)result;
NSString *accessGroup = dict[(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
return bundleSeedID;
}
Here is the Swift version of #David H answer:
static func bundleSeedID() -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "bundleSeedID" as AnyObject,
kSecAttrService as String: "" as AnyObject,
kSecReturnAttributes as String: kCFBooleanTrue
]
var result : AnyObject?
var status = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if status == errSecItemNotFound {
status = withUnsafeMutablePointer(to: &result) {
SecItemAdd(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
}
if status == noErr {
if let resultDict = result as? [String: Any], let accessGroup = resultDict[kSecAttrAccessGroup as String] as? String {
let components = accessGroup.components(separatedBy: ".")
return components.first
}else {
return nil
}
} else {
print("Error getting bundleSeedID to Keychain")
return nil
}
}
This is a good question but to achieve what you were intended to do, there could have been a solution
that does not require to retrieve the Bundle Seed ID.
From this article, about the same keychain wrapper you're using:
By default it will pick the first access-group specified in your
Entitlements.plist when writing and will search across all
access-groups when none is specified.
The key will then be search in all groups where access is granted.
So to solve your problem, you could add access group of all your bundle apps into your entitlements.plist instead of using a "shared stuff" group, put $(CFBundleIdentifier) as your first keychain group (your keychain wrapper will then write in this group) and you're all set
If you search in Xcode on your team's ID then you will see that this value is hosted in the build settings under the key DEVELOPMENT_TEAM.
You can retrieve this key by putting in your Info.plist file:
<key>DEVELOPMENT_TEAM</key>
<string>$(DEVELOPMENT_TEAM)</string>
Make sure to put this in every target's Info.plist file where you want to retrieve it using this code:
let teamID = Bundle.main.infoDictionary!["DEVELOPMENT_TEAM"] as! String
This solution will give you the team ID without the dot suffix.
The solution in https://stackoverflow.com/a/28714850/2743633 worked for me only to get the team ID from the main app target. It would not retrieve the team ID when doing the same for a Share Extension target.

Access App Identifier Prefix programmatically

How can I access the Bundle Seed ID/Team ID/App Identifier Prefix string programmatically? (These are all the same thing as far as I can tell).
I am using the UICKeychainStore keychain wrapper to persist data across several applications. Each of these applications has a shared keychain access group in their entitlement plists, and share the same provisioning profile. By default, the keychain services use the first access group in the plist as the access group to save data to. This looks like "AS234SDG.com.myCompany.SpecificApp" when I debug UICKeychainStore. I would like to set the access group to "AS234SDG.com.myCompany.SharedStuff", but I can't seem to locate how to get the "AS234SDG" string of the access group programmatically, and would like to avoid hard-coding it if possible.
Info.plist can have your own information and if you write a value with $(AppIdentifierPrefix), it is replaced to the real app identifier prefix at building phase.
So, try this:
In your Info.plist, add an info about app identifier prefix.
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
You can then retrieve it programmatically with Objective-C:
NSString *appIdentifierPrefix =
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"AppIdentifierPrefix"];
and with Swift:
let appIdentifierPrefix =
Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String
Note that appIdentifierPrefix ends with a period; e.g. AS234SDG.
You can programmatically retrieve the Bundle Seed ID by looking at the access group attribute (i.e. kSecAttrAccessGroup) of an existing KeyChain item. In the code below, I look up for an existing KeyChain entry and create one if it doesn't not exist. Once I have a KeyChain entry, I extract the access group information from it and return the access group's first component separated by "." (period) as the Bundle Seed ID.
+ (NSString *)bundleSeedID {
NSString *tempAccountName = #"bundleSeedID";
NSDictionary *query = #{
(__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount : tempAccountName,
(__bridge NSString *)kSecAttrService : #"",
(__bridge NSString *)kSecReturnAttributes: (__bridge NSNumber *)kCFBooleanTrue,
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess) {
return nil;
}
status = SecItemDelete((__bridge CFDictionaryRef)query); // remove temp item
NSDictionary *dict = (__bridge_transfer NSDictionary *)result;
NSString *accessGroup = dict[(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
return bundleSeedID;
}
Here is the Swift version of #David H answer:
static func bundleSeedID() -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "bundleSeedID" as AnyObject,
kSecAttrService as String: "" as AnyObject,
kSecReturnAttributes as String: kCFBooleanTrue
]
var result : AnyObject?
var status = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if status == errSecItemNotFound {
status = withUnsafeMutablePointer(to: &result) {
SecItemAdd(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
}
if status == noErr {
if let resultDict = result as? [String: Any], let accessGroup = resultDict[kSecAttrAccessGroup as String] as? String {
let components = accessGroup.components(separatedBy: ".")
return components.first
}else {
return nil
}
} else {
print("Error getting bundleSeedID to Keychain")
return nil
}
}
This is a good question but to achieve what you were intended to do, there could have been a solution
that does not require to retrieve the Bundle Seed ID.
From this article, about the same keychain wrapper you're using:
By default it will pick the first access-group specified in your
Entitlements.plist when writing and will search across all
access-groups when none is specified.
The key will then be search in all groups where access is granted.
So to solve your problem, you could add access group of all your bundle apps into your entitlements.plist instead of using a "shared stuff" group, put $(CFBundleIdentifier) as your first keychain group (your keychain wrapper will then write in this group) and you're all set
If you search in Xcode on your team's ID then you will see that this value is hosted in the build settings under the key DEVELOPMENT_TEAM.
You can retrieve this key by putting in your Info.plist file:
<key>DEVELOPMENT_TEAM</key>
<string>$(DEVELOPMENT_TEAM)</string>
Make sure to put this in every target's Info.plist file where you want to retrieve it using this code:
let teamID = Bundle.main.infoDictionary!["DEVELOPMENT_TEAM"] as! String
This solution will give you the team ID without the dot suffix.
The solution in https://stackoverflow.com/a/28714850/2743633 worked for me only to get the team ID from the main app target. It would not retrieve the team ID when doing the same for a Share Extension target.

iOS unique user identifier [duplicate]

This question already has answers here:
UIDevice uniqueIdentifier deprecated - What to do now?
(32 answers)
Closed 8 years ago.
I'm writting an application for iphone, which communicates with my server using REST. The main problem is, I need to identify user somehow. Not so long ago, we were allowed to use UDID, but now its not allowed anymore. So what should I use instead? I need some kind of identifier on iphone, so user will delete application, install it again, and he will get same id.
I used CFUUIDCreate() to create a UUID:
+ (NSString *)GetUUID {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
return [(NSString *)string autorelease];
}
Then set the above UUID to my NSString:
NSString *UUID = [nameofclasswhereGetUUIDclassmethodresides UUID];
I then stored that UUID to the Keychain using SSKeyChain
To set the UUID with SSKeyChain:
[SSKeychain setPassword:UUID forService:#"com.yourapp.yourcompany" account:#"user"];
To Retrieve it:
NSString *retrieveuuid = [SSKeychain passwordForService:#"com.yourapp.yourcompany" account:#"user"];
When you set the UUID to the Keychain, it will persist even if the user completely uninstalls the App and then installs it again.
To make sure ALL devices have the same UUID in the Keychain.
Setup your app to use iCloud.
Save the UUID that is in the Keychain to NSUserDefaults as well.
Pass the UUID in NSUserDefaults to the Cloud with Key-Value Data Store.
On App first run, Check if the Cloud Data is available and set the UUID in the Keychain on the New Device.
You now have a Unique Identifier that is persistent and shared/synced with all devices.
Firstly, the UDID is only deprecated in iOS 5. That doesn't mean it's gone (yet).
Secondly, you should ask yourself if you really need such a thing. What if the user gets a new device and installs your app on that? Same user, but the UDID has changed. Meanwhile, the original user might have sold his old device so now a completely new user installs your app and you think it's a different person based on the UDID.
If you don't need the UDID, use CFUUIDCreate() to create a unique ID and save it to the user defaults on the first launch (use CFUUIDCreateString() to convert the UUID to a string first). It will survive backups and restores and even come along with the original user when they switch to a new device. It's in many ways a better option that the UDID.
If you really need a unique device identifier (it doesn't sound like you do), go for the MAC address as pointed out in Suhail's answer.
I was updating my application that was working based only on Unique Identifier which supported iOS 4.3 and above. So,
1) I was unable to use [UIDevice currentDevice].uniqueIdentifier; as it was no longer available
2) I could not use [UIDevice currentDevice].identifierForVendor.UUIDString because it was Available in iOS 6.0 and later only and was unable to use for lower iOS versions.
3) The mac address was not an option as it wasn't allowed in iOS-7
4) OpenUDID was deprecated some time ago and also had issues with iOS-6.
5) Advertisement identifiers were also not available for iOS-5 and below
Finally this was what i did
a) Added SFHFKeychainUtils to the project
b) Generated CFUUID key String
CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
udidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
c) Saved it to Key Chain Utils or else it will generate a new Unique Each Time
Final Code
+ (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
Some people want to know more about the different options available, and if you do, take a look at the answer from #NSQuamber.java. If you want to know how to use the NSUUID and sync with iCloud, keep reading. This post ended up being more long-winded than I originally wanted, but I hope that it makes it clear for anyone taking these steps!
Using NSUUID
I use the NSUUID class to create the UUID:
NSUUID *uuid = [NSUUID UUID];
Then to create the string, you only need to call the UUIDString method:
NSString *uuidString = [uuid UUIDString];
or do it in one line:
NSString *uuidString = [[NSUUID UUID] UUIDString];
IMHO, this is much easier than trying to use CFUUIDCreate and have a method you have to maintain.
EDIT: I now use UICKeyChainStore
To set the UUID with UICKeyChainStore:
UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:#"com.sample.MyApp"];
keychain[#"com.sample.MyApp.user"] = userID;
To retrieve it:
UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:#"com.sample.MyApp"];
NSString *userID = keychain[#"com.sample.MyApp.user"];
I then stored that UUID to the Keychain using SSKeyChain
To set the UUID with SSKeyChain:
[SSKeychain setPassword:userID forService:#"com.sample.MyApp.user" account:#"com.sample.MyApp"];
To retrieve it:
NSString *userID = [SSKeychain passwordForService:#"com.sample.MyApp.user" account:#"com.sample.MyApp"];
When you set the UUID to the Keychain, it will persist even if the user completely uninstalls the App and then installs it again.
Syncing with iCloud
So it's useful to make sure that all the user's devices use the same UUID. This is to ensure that data is synchronized across all the devices, rather than each device thinking it is a unique user.
There were several questions in the comments for my answer on how synchronization would work, so now that I've got it all working, I'll provide more details.
Configuring iCloud/NSUbiquitousKeyValueStore Use
Click on your project at the top of the Project Navigator in Xcode.
Select Capabilities.
Turn on iCloud.
It should now look something like this:
Using NSUbiquitousKeyValueStore
Using iCloud is fairly simple. To write:
// 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];
To read:
// create the key to store the ID
NSString *userKey = #"com.sample.MyApp.user";
// read from iCloud
NSString *userID = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:userKey];
Before you can write the NSUbiquitousKeyValueStore documentation states that you are required to read from iCloud first. To force a read, call the following method:
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
To have your app receive notifications of changes in iCloud, add the following notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(iCloudStoreDidChange:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:[NSUbiquitousKeyValueStore defaultStore]];
Creating the UUID with iCloud
Combining NSUUID, SSKeychain and NSUbiquityKeyValueStore, here's my method for generating a user 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];
}
How to ensure that your User ID is in sync
Because writing to iCloud requires a download of any data in iCloud first, I put the synchronize call at the top of the (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method. I also added the notification registration there as well. That allows me to detect any changes from iCloud and handle them appropriately.
Here's a sample:
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;
}
}
}
When the application is launched or when it comes back to the foreground, I force a synchronization with iCloud and verify the integrity of the UUIDs.
- (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;
}
If which UUID came first matters
In my use case of my UserID, I assumed that the value in iCloud is the one to keep, since it would be the first UUID pushed to iCloud, regardless of which device generated the UUID first. Most of you would probably take the same path, since you won't really care which UUID it resolves to, as long as it resolves to a single one. For those of you who actually care about which came first, I suggest you store both the UUID and the timestamp generation ([[NSDate date] timeIntervalSince1970]) so that you can check to see which one is older:
// using dates
NSDate *uuid1Timestamp = [NSDate dateWithTimeIntervalSince1970:timestamp1];
NSDate *uuid2Timestamp = [NSDate dateWithTimeIntervalSince1970:timestamp2];
NSTimeInterval timeDifference = [uuid1 timeIntervalSinceDate:uuid2Timestamp];
// or just subtract
double timeDifference = timestamp1 - timestamp2;
There is a nice alternative on Github which generates a Unique Identifier based on a combination of Mac Address and the Bundle Identifier which works pretty well: UIDevice-with-UniqueIdentifier-for-iOS-5
In iOS7 Apple has introduced a read only property called "identifierForVendor" in the UIDevice class. If you decide to use it you should make note of the following,
This value could be nil if it is accessed before the user unlocks the device
The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them.
The value can also change when installing test builds using Xcode or when installing an app on a device using ad-hoc distribution.
If you need an identifier for advertising purposes, use the advertisingIdentifier property of ASIdentifierManager. However make note that point one discussed above is still true for this as well.
Source: https://developer.apple.com/library/ios/documentation/uikit/reference/UIDevice_Class/Reference/UIDevice.html#//apple_ref/occ/instp/UIDevice/identifierForVendor
This is a hot topic indeed. I have an app that I have to migrate because it used the UDID to name an XML file to be stored on a server. Then the device with the app would connect to the server and download its specific udid.xml and parse it to work.
Ive been thinking that indeed if the user moves to a new device, the app will break. So I really should use something else. The thing is, I don't use a database for the data. The data is simply stored in an xml format, one xml file per device stored on the cloud.
Im thinking the best thing would be to have the user fill out the data on the web, have php create a token on the fly which will not be stored in a database but rather sent to the user. The user can then input the token on the target device and retrieve the xml in question.
That would be my solution to the problem. Not sure how to implement the whole 'creating unique tokens' thing though.

How to change/reset iPhone simulator device ID?

How to change or reset an iPhone simulator device ID?
Solved.
#implementation UIDevice (ChangeUID)
- (NSString*)uniqueIdentifier
{
return #"test";
}
#end
If you just want to generate an UUID, say to tag an upload or communication to your server as being from a specific device you can use the CFUUID class to generate a UUID the first time your application is run,
NSString *uuid = nil;
CFUUID theUUID = CFUUIDCreate(kCFAllocatorDefault);
if (theUUID) {
uuid = NSMakeCollectable(CFUUIDCreateString(kCFAllocatorDefault, theUUID);
CFRelease(theUUID);
}
and then save this in your application preferences. This will then uniquely identify the users device, and it'll also work in the iPhone simulator.