Created
May 25, 2015 15:09
-
-
Save miguelcma/e8f291e54b025815ca46 to your computer and use it in GitHub Desktop.
iOS Unique Device ID that persists between app reinstalls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 |
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];
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Excellent!!!
Please take in account quard8 suggestion is case of any loop and crash, and smdmitry's if you want to use IFA.