New file |
| | |
| | | // |
| | | // FCUUID.m |
| | | // |
| | | // Created by Fabio Caccamo on 26/06/14. |
| | | // Copyright © 2016 Fabio Caccamo. All rights reserved. |
| | | // |
| | | |
| | | #import "FCUUID.h" |
| | | #import "UICKeyChainStore.h" |
| | | |
| | | |
| | | @implementation FCUUID |
| | | |
| | | |
| | | NSString *const FCUUIDsOfUserDevicesDidChangeNotification = @"FCUUIDsOfUserDevicesDidChangeNotification"; |
| | | |
| | | |
| | | NSString *const _uuidForInstallationKey = @"fc_uuidForInstallation"; |
| | | NSString *const _uuidForDeviceKey = @"fc_uuidForDevice"; |
| | | NSString *const _uuidsOfUserDevicesKey = @"fc_uuidsOfUserDevices"; |
| | | NSString *const _uuidsOfUserDevicesToggleKey = @"fc_uuidsOfUserDevicesToggle"; |
| | | |
| | | |
| | | +(FCUUID *)sharedInstance |
| | | { |
| | | static FCUUID *instance = nil; |
| | | static dispatch_once_t token; |
| | | |
| | | dispatch_once(&token, ^{ |
| | | instance = [[self alloc] init]; |
| | | }); |
| | | |
| | | return instance; |
| | | } |
| | | |
| | | |
| | | -(instancetype)init |
| | | { |
| | | self = [super init]; |
| | | |
| | | if(self) |
| | | { |
| | | [self uuidsOfUserDevices_iCloudInit]; |
| | | } |
| | | |
| | | return self; |
| | | } |
| | | |
| | | |
| | | -(NSString *)_getOrCreateValueForKey:(NSString *)key defaultValue:(NSString *)defaultValue userDefaults:(BOOL)userDefaults keychain:(BOOL)keychain service:(NSString *)service accessGroup:(NSString *)accessGroup synchronizable:(BOOL)synchronizable |
| | | { |
| | | NSString *value = [self _getValueForKey:key userDefaults:userDefaults keychain:keychain service:service accessGroup:accessGroup]; |
| | | |
| | | if(!value){ |
| | | value = defaultValue; |
| | | } |
| | | |
| | | if(!value){ |
| | | value = [self uuid]; |
| | | } |
| | | |
| | | [self _setValue:value forKey:key userDefaults:userDefaults keychain:keychain service:service accessGroup:accessGroup synchronizable:synchronizable]; |
| | | |
| | | return value; |
| | | } |
| | | |
| | | |
| | | -(NSString *)_getValueForKey:(NSString *)key userDefaults:(BOOL)userDefaults keychain:(BOOL)keychain service:(NSString *)service accessGroup:(NSString *)accessGroup |
| | | { |
| | | NSString *value = nil; |
| | | |
| | | if(!value && keychain ){ |
| | | value = [UICKeyChainStore stringForKey:key service:service accessGroup:accessGroup]; |
| | | } |
| | | |
| | | if(!value && userDefaults ){ |
| | | value = [[NSUserDefaults standardUserDefaults] stringForKey:key]; |
| | | } |
| | | |
| | | return value; |
| | | } |
| | | |
| | | |
| | | -(void)_setValue:(NSString *)value forKey:(NSString *)key userDefaults:(BOOL)userDefaults keychain:(BOOL)keychain service:(NSString *)service accessGroup:(NSString *)accessGroup synchronizable:(BOOL)synchronizable |
| | | { |
| | | if( value && userDefaults ){ |
| | | [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; |
| | | [[NSUserDefaults standardUserDefaults] synchronize]; |
| | | } |
| | | |
| | | if( value && keychain ){ |
| | | UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup]; |
| | | [keychain setSynchronizable:synchronizable]; |
| | | [keychain setString:value forKey:key]; |
| | | } |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuid |
| | | { |
| | | //also known as uuid/universallyUniqueIdentifier |
| | | |
| | | CFUUIDRef uuidRef = CFUUIDCreate(NULL); |
| | | CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef); |
| | | CFRelease(uuidRef); |
| | | |
| | | NSString *uuidValue = (__bridge_transfer NSString *)uuidStringRef; |
| | | uuidValue = [uuidValue lowercaseString]; |
| | | uuidValue = [uuidValue stringByReplacingOccurrencesOfString:@"-" withString:@""]; |
| | | return uuidValue; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForKey:(id<NSCopying>)key |
| | | { |
| | | if( _uuidForKey == nil ){ |
| | | _uuidForKey = [[NSMutableDictionary alloc] init]; |
| | | } |
| | | |
| | | NSString *uuidValue = [_uuidForKey objectForKey:key]; |
| | | |
| | | if( uuidValue == nil ){ |
| | | uuidValue = [self uuid]; |
| | | |
| | | [_uuidForKey setObject:uuidValue forKey:key]; |
| | | } |
| | | |
| | | return uuidValue; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForSession |
| | | { |
| | | if( _uuidForSession == nil ){ |
| | | _uuidForSession = [self uuid]; |
| | | } |
| | | |
| | | return _uuidForSession; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForInstallation |
| | | { |
| | | if( _uuidForInstallation == nil ){ |
| | | _uuidForInstallation = [self _getOrCreateValueForKey:_uuidForInstallationKey defaultValue:nil userDefaults:YES keychain:NO service:nil accessGroup:nil synchronizable:NO]; |
| | | } |
| | | |
| | | return _uuidForInstallation; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForVendor |
| | | { |
| | | if( _uuidForVendor == nil ){ |
| | | _uuidForVendor = [[[[[UIDevice currentDevice] identifierForVendor] UUIDString] lowercaseString] stringByReplacingOccurrencesOfString:@"-" withString:@""]; |
| | | } |
| | | |
| | | return _uuidForVendor; |
| | | } |
| | | |
| | | |
| | | -(void)uuidForDevice_updateWithValue:(NSString *)value |
| | | { |
| | | _uuidForDevice = [NSString stringWithString:value]; |
| | | [self _setValue:_uuidForDevice forKey:_uuidForDeviceKey userDefaults:YES keychain:YES service:nil accessGroup:nil synchronizable:NO]; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForDevice |
| | | { |
| | | //also known as udid/uniqueDeviceIdentifier but this doesn't persists to system reset |
| | | |
| | | if( _uuidForDevice == nil ){ |
| | | _uuidForDevice = [self _getOrCreateValueForKey:_uuidForDeviceKey defaultValue:nil userDefaults:YES keychain:YES service:nil accessGroup:nil synchronizable:NO]; |
| | | } |
| | | |
| | | return _uuidForDevice; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForDeviceMigratingValue:(NSString *)value commitMigration:(BOOL)commitMigration |
| | | { |
| | | if([self uuidValueIsValid:value]) |
| | | { |
| | | NSString *oldValue = [self uuidForDevice]; |
| | | NSString *newValue = [NSString stringWithString:value]; |
| | | |
| | | if([oldValue isEqualToString:newValue]) |
| | | { |
| | | return oldValue; |
| | | } |
| | | |
| | | if(commitMigration) |
| | | { |
| | | [self uuidForDevice_updateWithValue:newValue]; |
| | | |
| | | NSMutableOrderedSet *uuidsOfUserDevicesSet = [[NSMutableOrderedSet alloc] initWithArray:[self uuidsOfUserDevices]]; |
| | | [uuidsOfUserDevicesSet addObject:newValue]; |
| | | [uuidsOfUserDevicesSet removeObject:oldValue]; |
| | | |
| | | [self uuidsOfUserDevices_updateWithValue:[uuidsOfUserDevicesSet array]]; |
| | | [self uuidsOfUserDevices_iCloudSync]; |
| | | |
| | | return [self uuidForDevice]; |
| | | } |
| | | else { |
| | | return oldValue; |
| | | } |
| | | } |
| | | else { |
| | | [NSException raise:@"Invalid uuid to migrate" format:@"uuid value should be a string of 32 or 36 characters."]; |
| | | |
| | | return nil; |
| | | } |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key commitMigration:(BOOL)commitMigration |
| | | { |
| | | return [self uuidForDeviceMigratingValueForKey:key service:nil accessGroup:nil commitMigration:commitMigration]; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key service:(NSString *)service commitMigration:(BOOL)commitMigration |
| | | { |
| | | return [self uuidForDeviceMigratingValueForKey:key service:service accessGroup:nil commitMigration:commitMigration]; |
| | | } |
| | | |
| | | |
| | | -(NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup commitMigration:(BOOL)commitMigration |
| | | { |
| | | NSString *uuidToMigrate = [self _getValueForKey:key userDefaults:YES keychain:YES service:service accessGroup:accessGroup]; |
| | | |
| | | return [self uuidForDeviceMigratingValue:uuidToMigrate commitMigration:commitMigration]; |
| | | } |
| | | |
| | | |
| | | -(void)uuidsOfUserDevices_iCloudInit |
| | | { |
| | | _uuidsOfUserDevices_iCloudAvailable = NO; |
| | | |
| | | if(NSClassFromString(@"NSUbiquitousKeyValueStore")) |
| | | { |
| | | NSUbiquitousKeyValueStore *iCloud = [NSUbiquitousKeyValueStore defaultStore]; |
| | | |
| | | if(iCloud) |
| | | { |
| | | _uuidsOfUserDevices_iCloudAvailable = YES; |
| | | |
| | | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(uuidsOfUserDevices_iCloudChange:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil]; |
| | | |
| | | [self uuidsOfUserDevices_iCloudSync]; |
| | | } |
| | | else { |
| | | //NSLog(@"iCloud not available"); |
| | | } |
| | | } |
| | | else { |
| | | //NSLog(@"iOS < 5"); |
| | | } |
| | | } |
| | | |
| | | |
| | | -(void)uuidsOfUserDevices_iCloudSync |
| | | { |
| | | if( _uuidsOfUserDevices_iCloudAvailable ) |
| | | { |
| | | NSUbiquitousKeyValueStore *iCloud = [NSUbiquitousKeyValueStore defaultStore]; |
| | | |
| | | //if keychain contains more device identifiers than icloud, maybe that icloud has been empty, so re-write these identifiers to iCloud |
| | | for ( NSString *uuidOfUserDevice in [self uuidsOfUserDevices] ) |
| | | { |
| | | NSString *uuidOfUserDeviceAsKey = [NSString stringWithFormat:@"%@_%@", _uuidForDeviceKey, uuidOfUserDevice]; |
| | | |
| | | if(![[iCloud stringForKey:uuidOfUserDeviceAsKey] isEqualToString:uuidOfUserDevice]){ |
| | | [iCloud setString:uuidOfUserDevice forKey:uuidOfUserDeviceAsKey]; |
| | | } |
| | | } |
| | | |
| | | //toggle a boolean value to force notification on other devices, useful for debug |
| | | [iCloud setBool:![iCloud boolForKey:_uuidsOfUserDevicesToggleKey] forKey:_uuidsOfUserDevicesToggleKey]; |
| | | [iCloud synchronize]; |
| | | } |
| | | } |
| | | |
| | | |
| | | -(void)uuidsOfUserDevices_iCloudChange:(NSNotification *)notification |
| | | { |
| | | if( _uuidsOfUserDevices_iCloudAvailable ) |
| | | { |
| | | NSMutableOrderedSet *uuidsSet = [[NSMutableOrderedSet alloc] initWithArray:[self uuidsOfUserDevices]]; |
| | | NSInteger uuidsCount = [uuidsSet count]; |
| | | |
| | | NSUbiquitousKeyValueStore *iCloud = [NSUbiquitousKeyValueStore defaultStore]; |
| | | NSDictionary *iCloudDict = [iCloud dictionaryRepresentation]; |
| | | |
| | | //NSLog(@"uuidsOfUserDevicesSync: %@", iCloudDict); |
| | | |
| | | [iCloudDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { |
| | | |
| | | NSString *uuidKey = (NSString *)key; |
| | | |
| | | if([uuidKey rangeOfString:_uuidForDeviceKey].location == 0) |
| | | { |
| | | if([obj isKindOfClass:[NSString class]]) |
| | | { |
| | | NSString *uuidValue = (NSString *)obj; |
| | | |
| | | if([uuidKey rangeOfString:uuidValue].location != NSNotFound && [self uuidValueIsValid:uuidValue]) |
| | | { |
| | | //NSLog(@"uuid: %@", uuidValue); |
| | | |
| | | [uuidsSet addObject:uuidValue]; |
| | | } |
| | | else { |
| | | //NSLog(@"invalid uuid"); |
| | | } |
| | | } |
| | | } |
| | | }]; |
| | | |
| | | if([uuidsSet count] > uuidsCount) |
| | | { |
| | | [self uuidsOfUserDevices_updateWithValue:[uuidsSet array]]; |
| | | |
| | | NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[self uuidsOfUserDevices] forKey:@"uuidsOfUserDevices"]; |
| | | [[NSNotificationCenter defaultCenter] postNotificationName:FCUUIDsOfUserDevicesDidChangeNotification object:self userInfo:userInfo]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | -(void)uuidsOfUserDevices_updateWithValue:(NSArray *)value |
| | | { |
| | | _uuidsOfUserDevices = [value componentsJoinedByString:@"|"]; |
| | | [self _setValue:_uuidsOfUserDevices forKey:_uuidsOfUserDevicesKey userDefaults:YES keychain:YES service:nil accessGroup:nil synchronizable:YES]; |
| | | } |
| | | |
| | | |
| | | -(NSArray *)uuidsOfUserDevices |
| | | { |
| | | if( _uuidsOfUserDevices == nil ){ |
| | | _uuidsOfUserDevices = [self _getOrCreateValueForKey:_uuidsOfUserDevicesKey defaultValue:[self uuidForDevice] userDefaults:YES keychain:YES service:nil accessGroup:nil synchronizable:YES]; |
| | | } |
| | | |
| | | return [_uuidsOfUserDevices componentsSeparatedByString:@"|"]; |
| | | } |
| | | |
| | | |
| | | -(NSArray *)uuidsOfUserDevicesExcludingCurrentDevice |
| | | { |
| | | NSMutableArray *uuids = [NSMutableArray arrayWithArray:[self uuidsOfUserDevices]]; |
| | | [uuids removeObject:[self uuidForDevice]]; |
| | | return [NSArray arrayWithArray:uuids]; |
| | | } |
| | | |
| | | |
| | | -(BOOL)uuidValueIsValid:(NSString *)uuidValue |
| | | { |
| | | if(uuidValue != nil) |
| | | { |
| | | NSString *uuidPattern = @"^[0-9a-f]{32}|[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$"; |
| | | NSRegularExpression *uuidRegExp = [NSRegularExpression regularExpressionWithPattern:uuidPattern options:NSRegularExpressionCaseInsensitive error:nil]; |
| | | |
| | | NSRange uuidValueRange = NSMakeRange(0, [uuidValue length]); |
| | | NSRange uuidMatchRange = [uuidRegExp rangeOfFirstMatchInString:uuidValue options:0 range:uuidValueRange]; |
| | | NSString *uuidMatchValue; |
| | | |
| | | if(!NSEqualRanges(uuidMatchRange, NSMakeRange(NSNotFound, 0))) |
| | | { |
| | | uuidMatchValue = [uuidValue substringWithRange:uuidMatchRange]; |
| | | |
| | | if([uuidMatchValue isEqualToString:uuidValue]) |
| | | { |
| | | return YES; |
| | | } |
| | | else { |
| | | return NO; |
| | | } |
| | | } |
| | | else { |
| | | return NO; |
| | | } |
| | | } |
| | | else { |
| | | return NO; |
| | | } |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuid |
| | | { |
| | | return [[self sharedInstance] uuid]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForKey:(id<NSCopying>)key |
| | | { |
| | | return [[self sharedInstance] uuidForKey:key]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForSession |
| | | { |
| | | return [[self sharedInstance] uuidForSession]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForInstallation |
| | | { |
| | | return [[self sharedInstance] uuidForInstallation]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForVendor |
| | | { |
| | | return [[self sharedInstance] uuidForVendor]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForDevice |
| | | { |
| | | return [[self sharedInstance] uuidForDevice]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForDeviceMigratingValue:(NSString *)value commitMigration:(BOOL)commitMigration |
| | | { |
| | | return [[self sharedInstance] uuidForDeviceMigratingValue:value commitMigration:commitMigration]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key commitMigration:(BOOL)commitMigration |
| | | { |
| | | return [[self sharedInstance] uuidForDeviceMigratingValueForKey:key service:nil accessGroup:nil commitMigration:commitMigration]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key service:(NSString *)service commitMigration:(BOOL)commitMigration |
| | | { |
| | | return [[self sharedInstance] uuidForDeviceMigratingValueForKey:key service:service accessGroup:nil commitMigration:commitMigration]; |
| | | } |
| | | |
| | | |
| | | +(NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup commitMigration:(BOOL)commitMigration |
| | | { |
| | | return [[self sharedInstance] uuidForDeviceMigratingValueForKey:key service:service accessGroup:accessGroup commitMigration:commitMigration]; |
| | | } |
| | | |
| | | |
| | | +(NSArray *)uuidsOfUserDevices |
| | | { |
| | | return [[self sharedInstance] uuidsOfUserDevices]; |
| | | } |
| | | |
| | | |
| | | +(NSArray *)uuidsOfUserDevicesExcludingCurrentDevice |
| | | { |
| | | return [[self sharedInstance] uuidsOfUserDevicesExcludingCurrentDevice]; |
| | | } |
| | | |
| | | |
| | | +(BOOL)uuidValueIsValid:(NSString *)uuidValue |
| | | { |
| | | return [[self sharedInstance] uuidValueIsValid:uuidValue]; |
| | | } |
| | | |
| | | |
| | | @end |