I'd like to offer this as a solution. It requires no third party code. It reads or writes to the KeyChain if it absolutely needs to, so it's more efficient that the solutions provided so far. Also, the UUID is written to a keychain access group so that you can share this UUID between your apps. Simply change kKeyChainVendorIDAccessGroup
at the start of the method.
+(NSUUID *)persistentIdentifierForVendor
{
static NSString * const kKeyChainVendorID = @"co.cwbrn.PersistentIdentifier";
static NSString * const kKeyChainVendorIDAccessGroup = @"<AppIdentifier>.<keychain-access-group-identifier>";
// First, check NSUserDefaults so that we're not hitting the KeyChain every single time
NSString *uuidString = [[NSUserDefaults standardUserDefaults] stringForKey:kKeyChainVendorIDGroup];
BOOL vendorIDMissingFromUserDefaults = (uuidString == nil || uuidString.length == 0);
if (vendorIDMissingFromUserDefaults) {
// Check to see if a UUID is stored in the KeyChain
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: kKeyChainVendorID,
(__bridge id)kSecAttrService: kKeyChainVendorID,
(__bridge id)kSecAttrAccessGroup: kKeyChainVendorIDAccessGroup,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
(__bridge id)kSecReturnAttributes: (__bridge id)kCFBooleanTrue
};
CFTypeRef attributesRef = NULL;
OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesRef);
if (result == noErr) {
// There is a UUID, so try to retrieve it
NSDictionary *attributes = (__bridge_transfer NSDictionary *)attributesRef;
NSMutableDictionary *valueQuery = [NSMutableDictionary dictionaryWithDictionary:attributes];
[valueQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[valueQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
CFTypeRef passwordDataRef = NULL;
OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)valueQuery, &passwordDataRef);
if (result == noErr) {
NSData *passwordData = (__bridge_transfer NSData *)passwordDataRef;
uuidString = [[NSString alloc] initWithBytes:[passwordData bytes]
length:[passwordData length]
encoding:NSUTF8StringEncoding];
}
}
}
// Failed to read the UUID from the KeyChain, so create a new UUID and store it
if (uuidString == nil || uuidString.length == 0) {
// Generate the new UIID
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
CFRelease(uuidRef);
// Now store it in the KeyChain
NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: kKeyChainVendorID,
(__bridge id)kSecAttrService: kKeyChainVendorID,
(__bridge id)kSecAttrAccessGroup: kKeyChainVendorIDAccessGroup,
(__bridge id)kSecAttrLabel: @"",
(__bridge id)kSecAttrDescription: @"",
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock,
(__bridge id)kSecValueData: [uuidString dataUsingEncoding:NSUTF8StringEncoding]
};
OSStatus result = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (result != noErr) {
NSLog(@"ERROR: Couldn't add to the Keychain. Result = %ld; Query = %@", result, query);
return nil;
}
}
// Save UUID to NSUserDefaults so that we can avoid the KeyChain next time
if (vendorIDMissingFromUserDefaults) {
[[NSUserDefaults standardUserDefaults] setObject:uuidString forKey:kKeyChainVendorIDGroup];
}
return [[NSUUID alloc] initWithUUIDString:uuidString];
}