199 lines
6.2 KiB
Objective-C
199 lines
6.2 KiB
Objective-C
// Initial work from:
|
|
// https://gist.github.com/miguelcma/e8f291e54b025815ca46
|
|
// Modified as the original version crashes.
|
|
|
|
#import "DeviceUID.h"
|
|
|
|
@import UIKit;
|
|
|
|
@interface DeviceUID ()
|
|
|
|
@property(nonatomic, strong, readonly) NSString *uid;
|
|
|
|
@end
|
|
|
|
NSString * const UIDKey = @"deviceUID";
|
|
|
|
@implementation DeviceUID
|
|
|
|
@synthesize uid = _uid;
|
|
|
|
#pragma mark - Public methods
|
|
|
|
+ (NSString *)uid {
|
|
return [[[DeviceUID alloc] init] uid];
|
|
}
|
|
|
|
+ (NSString *)syncUid {
|
|
return [[[DeviceUID alloc] init] syncUid];
|
|
}
|
|
|
|
#pragma mark - Instance methods
|
|
|
|
- (id)init:(NSString *)key {
|
|
self = [super init];
|
|
if (self) {
|
|
_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 saveIfNeed];
|
|
return _uid;
|
|
}
|
|
|
|
/*! Persist Apple IFV (Identifier for Vendor) or random UUID as Device UID.
|
|
*/
|
|
- (NSString *)syncUid {
|
|
_uid = [[self class] appleIFV];
|
|
if (!_uid) _uid = [[self class] randomUUID];
|
|
[self save];
|
|
return _uid;
|
|
}
|
|
|
|
/*! Persist UID to NSUserDefaults and Keychain
|
|
*/
|
|
- (void)save {
|
|
[DeviceUID setValue:_uid forUserDefaultsKey:UIDKey];
|
|
[DeviceUID updateValue:_uid forKeychainKey:UIDKey inService:UIDKey];
|
|
}
|
|
|
|
/*! Persist UID to NSUserDefaults and Keychain, if not yet saved
|
|
*/
|
|
- (void)saveIfNeed {
|
|
if (![DeviceUID valueForUserDefaultsKey:UIDKey]) {
|
|
[DeviceUID setValue:_uid forUserDefaultsKey:UIDKey];
|
|
}
|
|
if (![DeviceUID valueForKeychainKey:UIDKey service:UIDKey]) {
|
|
[DeviceUID setValue:_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)kSecAttrAccessibleAfterFirstUnlock;
|
|
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];
|
|
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)keychainItem, NULL);
|
|
|
|
if (status == errSecDuplicateItem) {
|
|
[DeviceUID deleteValue:key inService:service];
|
|
status = SecItemAdd((__bridge CFDictionaryRef)keychainItem, NULL);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/*! Updates
|
|
* param1
|
|
* param2
|
|
*/
|
|
+ (OSStatus)updateValue:(NSString *)value forKeychainKey:(NSString *)key inService:(NSString *)service {
|
|
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
(__bridge id)kSecClassGenericPassword, kSecClass,
|
|
key, kSecAttrAccount,
|
|
service, kSecAttrService,
|
|
nil];
|
|
|
|
NSDictionary *attributesToUpdate = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[value dataUsingEncoding:NSUTF8StringEncoding], kSecValueData,
|
|
nil];
|
|
|
|
return SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributesToUpdate);
|
|
}
|
|
|
|
+ (OSStatus)deleteValue:(NSString *)key inService:(NSString *)service {
|
|
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
(__bridge id)kSecClassGenericPassword, kSecClass,
|
|
key, kSecAttrAccount,
|
|
service, kSecAttrService,
|
|
nil];
|
|
|
|
OSStatus status= SecItemDelete((__bridge CFDictionaryRef)query);
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
+ (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 *)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
|