-
-
Save miguelcma/e8f291e54b025815ca46 to your computer and use it in GitHub Desktop.
| /* DeviceUID.h | |
| #import <Foundation/Foundation.h> | |
| @interface DeviceUID : NSObject | |
| + (NSString *)uid; | |
| @end | |
| */ | |
| // Device.m | |
| #import "DeviceUID.h" | |
| @import UIKit; | |
| @interface DeviceUID () | |
| @property(nonatomic, strong, readonly) NSString *uidKey; | |
| @property(nonatomic, strong, readonly) NSString *uid; | |
| @end | |
| @implementation DeviceUID | |
| @synthesize uid = _uid; | |
| #pragma mark - Public methods | |
| + (NSString *)uid { | |
| return [[[DeviceUID alloc] initWithKey:@"deviceUID"] uid]; | |
| } | |
| #pragma mark - Instance methods | |
| - (id)initWithKey:(NSString *)key { | |
| self = [super init]; | |
| if (self) { | |
| _uidKey = key; | |
| _uid = nil; | |
| } | |
| return self; | |
| } | |
| /*! Returns the Device UID. | |
| The UID is obtained in a chain of fallbacks: | |
| - Keychain | |
| - NSUserDefaults | |
| - Apple IFV (Identifier for Vendor) | |
| - Generate a random UUID if everything else is unavailable | |
| At last, the UID is persisted if needed to. | |
| */ | |
| - (NSString *)uid { | |
| if (!_uid) _uid = [[self class] valueForKeychainKey:_uidKey service:_uidKey]; | |
| if (!_uid) _uid = [[self class] valueForUserDefaultsKey:_uidKey]; | |
| if (!_uid) _uid = [[self class] appleIFV]; | |
| if (!_uid) _uid = [[self class] randomUUID]; | |
| [self save]; | |
| return _uid; | |
| } | |
| /*! Persist UID to NSUserDefaults and Keychain, if not yet saved | |
| */ | |
| - (void)save { | |
| if (![DeviceUID valueForUserDefaultsKey:_uidKey]) { | |
| [DeviceUID setValue:self.uid forUserDefaultsKey:_uidKey]; | |
| } | |
| if (![DeviceUID valueForKeychainKey:_uidKey service:_uidKey]) { | |
| [DeviceUID setValue:self.uid forKeychainKey:_uidKey inService:_uidKey]; | |
| } | |
| } | |
| #pragma mark - Keychain methods | |
| /*! Create as generic NSDictionary to be used to query and update Keychain items. | |
| * param1 | |
| * param2 | |
| */ | |
| + (NSMutableDictionary *)keychainItemForKey:(NSString *)key service:(NSString *)service { | |
| NSMutableDictionary *keychainItem = [[NSMutableDictionary alloc] init]; | |
| keychainItem[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; | |
| keychainItem[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAlways; | |
| keychainItem[(__bridge id)kSecAttrAccount] = key; | |
| keychainItem[(__bridge id)kSecAttrService] = service; | |
| return keychainItem; | |
| } | |
| /*! Sets | |
| * param1 | |
| * param2 | |
| */ | |
| + (OSStatus)setValue:(NSString *)value forKeychainKey:(NSString *)key inService:(NSString *)service { | |
| NSMutableDictionary *keychainItem = [[self class] keychainItemForKey:key service:service]; | |
| keychainItem[(__bridge id)kSecValueData] = [value dataUsingEncoding:NSUTF8StringEncoding]; | |
| return SecItemAdd((__bridge CFDictionaryRef)keychainItem, NULL); | |
| } | |
| + (NSString *)valueForKeychainKey:(NSString *)key service:(NSString *)service { | |
| OSStatus status; | |
| NSMutableDictionary *keychainItem = [[self class] keychainItemForKey:key service:service]; | |
| keychainItem[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue; | |
| keychainItem[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue; | |
| CFDictionaryRef result = nil; | |
| status = SecItemCopyMatching((__bridge CFDictionaryRef)keychainItem, (CFTypeRef *)&result); | |
| if (status != noErr) { | |
| return nil; | |
| } | |
| NSDictionary *resultDict = (__bridge_transfer NSDictionary *)result; | |
| NSData *data = resultDict[(__bridge id)kSecValueData]; | |
| if (!data) { | |
| return nil; | |
| } | |
| return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; | |
| } | |
| #pragma mark - NSUserDefaults methods | |
| + (BOOL)setValue:(NSString *)value forUserDefaultsKey:(NSString *)key { | |
| [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; | |
| return [[NSUserDefaults standardUserDefaults] synchronize]; | |
| } | |
| + (NSString *)valueForUserDefaultsKey:(NSString *)key { | |
| return [[NSUserDefaults standardUserDefaults] objectForKey:key]; | |
| } | |
| #pragma mark - UID Generation methods | |
| + (NSString *)appleIFA { | |
| NSString *ifa = nil; | |
| Class ASIdentifierManagerClass = NSClassFromString(@"ASIdentifierManager"); | |
| if (ASIdentifierManagerClass) { // a dynamic way of checking if AdSupport.framework is available | |
| SEL sharedManagerSelector = NSSelectorFromString(@"sharedManager"); | |
| id sharedManager = ((id (*)(id, SEL))[ASIdentifierManagerClass methodForSelector:sharedManagerSelector])(ASIdentifierManagerClass, sharedManagerSelector); | |
| SEL advertisingIdentifierSelector = NSSelectorFromString(@"advertisingIdentifier"); | |
| NSUUID *advertisingIdentifier = ((NSUUID* (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector); | |
| ifa = [advertisingIdentifier UUIDString]; | |
| } | |
| return ifa; | |
| } | |
| + (NSString *)appleIFV { | |
| if(NSClassFromString(@"UIDevice") && [UIDevice instancesRespondToSelector:@selector(identifierForVendor)]) { | |
| // only available in iOS >= 6.0 | |
| return [[UIDevice currentDevice].identifierForVendor UUIDString]; | |
| } | |
| return nil; | |
| } | |
| + (NSString *)randomUUID { | |
| if(NSClassFromString(@"NSUUID")) { | |
| return [[NSUUID UUID] UUIDString]; | |
| } | |
| CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); | |
| CFStringRef cfuuid = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); | |
| CFRelease(uuidRef); | |
| NSString *uuid = [((__bridge NSString *) cfuuid) copy]; | |
| CFRelease(cfuuid); | |
| return uuid; | |
| } | |
| @end |
This is excellent! I can't thank you enough, as this came at the perfect time (nearly submitted by app by using the advertising identifier, which would have resulted in a rejection). 👍 👍
What license is this released under? I can't use this without an unrestrictive license and no license defaults to original authors retaining copyright.
This is great, thanks ^ ^
Also, the method which returns the Device UID is missing appleIFA, should be like:
- (NSString *)uid {
if (!_uid) _uid = [[self class] valueForKeychainKey:_uidKey service:_uidKey];
if (!_uid) _uid = [[self class] valueForUserDefaultsKey:_uidKey];
if (!_uid) _uid = [[self class] appleIFA];
if (!_uid) _uid = [[self class] appleIFV];
if (!_uid) _uid = [[self class] randomUUID];
[self save];
return _uid;
}What is the license under which this is available
This is Apache license you can see here: https://blog.onliquid.com/persistent-device-unique-identifier-ios-keychain/
Excellent!!!
Please take in account quard8 suggestion is case of any loop and crash, and smdmitry's if you want to use IFA.
Thats what I need :)
Thank you
I took quard8 suggestion to avoid crush
Has anyone used this with iOS10 beta? It's returning 00000000-0000-0000-0000-000000000000 from the advertisingIdentifier but identifierForVendor seems to work even when I set "Limit Ad Tracking" to YES.
- (NSString *)uid {
if (!_uid) _uid = [[self class] valueForKeychainKey:_uidKey service:_uidKey];
if (!_uid) _uid = [[self class] valueForUserDefaultsKey:_uidKey];
if (!_uid) _uid = [[self class] appleIFA];
if (!_uid || [_uid isEqualToString:@"00000000-0000-0000-0000-000000000000"]) _uid = [[self class] appleIFV];
if (!_uid || [_uid isEqualToString:@"00000000-0000-0000-0000-000000000000"]) _uid = [[self class] randomUUID];
[self save];
return _uid;
}I made a PR with this (incl. the fixes to make it not crash) over at react-native-device-info/react-native-device-info#58 -- can we all contribute there so its easy for us all to reuse our findings?
I got a different string using NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
You have small bug, which run app in infinity loop and when crush.
Should be:
When you access self.uid, you actually accessing "uid" method.