Skip to content

Instantly share code, notes, and snippets.

@miguelcma
Created May 25, 2015 15:09
Show Gist options
  • Save miguelcma/e8f291e54b025815ca46 to your computer and use it in GitHub Desktop.
Save miguelcma/e8f291e54b025815ca46 to your computer and use it in GitHub Desktop.
iOS Unique Device ID that persists between app reinstalls
/* 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
@quard8
Copy link

quard8 commented May 28, 2015

You have small bug, which run app in infinity loop and when crush.

- (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];
    }
}

Should be:

- (void)save {
    if (![DeviceUID valueForUserDefaultsKey:_uidKey]) {
        [DeviceUID setValue:_uid forUserDefaultsKey:_uidKey];
    }
    if (![DeviceUID valueForKeychainKey:_uidKey service:_uidKey]) {
        [DeviceUID setValue:_uid forKeychainKey:_uidKey inService:_uidKey];
    }
}

When you access self.uid, you actually accessing "uid" method.

@timgcarlson
Copy link

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). 👍 👍

@ajubbal
Copy link

ajubbal commented Aug 17, 2015

What license is this released under? I can't use this without an unrestrictive license and no license defaults to original authors retaining copyright.

@xieweizhi
Copy link

This is great, thanks ^ ^

@smdmitry
Copy link

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;
}

@gbonda
Copy link

gbonda commented Feb 6, 2016

What is the license under which this is available

@dzungpv
Copy link

dzungpv commented Apr 10, 2016

@synapse78
Copy link

Excellent!!!
Please take in account quard8 suggestion is case of any loop and crash, and smdmitry's if you want to use IFA.

@yahyaalshaar
Copy link

Thats what I need :)
Thank you

I took quard8 suggestion to avoid crush

@cthomaschase
Copy link

cthomaschase commented Jun 30, 2016

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;
}

@mehcode
Copy link

mehcode commented Jul 3, 2016

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?

@forgetMind
Copy link

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