Why does iOS get a new identifierForVendor when app updates? - ios

Every time my app is updated from the App Store some small number of the users get a new identifierForVendor for some reason. My users don't sign up or login. They are all anonymous so I need to separate them through their vendor IDs.
I've considered that there could've been insufficient space on some devices, resulting in the app being deleted and reinstalled, but that's not the case since in the last update a friend of mine had over 2GB of empty space.
I know that the identifierForVendor is changed for a user who deletes and reinstalls the app. But that's not the case here, as the app is just updated.
What could the problem be? The weird part is that this hasn't happened for development team devices yet. Or there are still users who haven't experienced this bug after countless app updates, OS updates, etc. This only happens to a small percentage of the users. All users are iOS7+ and it happens for different device models and iOS versions.
I use this code to get their ID:
static let DeviceId = UIDevice.currentDevice().identifierForVendor.UUIDString
Then I save them into NSUserDefaults:
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "User" + DeviceId)
NSUserDefaults.standardUserDefaults().synchronize()
Then I check if the user exists, at every new login:
static func doesUserExist() -> Bool {
var userDefaultValue: AnyObject? = NSUserDefaults.standardUserDefaults().valueForKey("User" + DeviceId)
if defaultValue == true {
println("Userdefaults already has this guy, moving on")
FirstTime = false
return true
} else {
println("First time in the app!")
FirstTime = true
return false
}
}
If the user does exist it starts the login process. If the user does not exist it shows them the signup process. I am using Parse.com as backend and the deviceID is used as a username. When that small amount of users experience this bug, I see a new username and a new account created.

There was a bug affecting the calculation of identifierForVendor when updating an app from app store between May and July. Apple has claimed they have already solved the issue and pushing another update should restore the original value before the critical date.
Reference: https://openradar.appspot.com/22677034
Take note that some users still have observed this issue even updating weeks after July. So it is still possible that bug is still there for some or it could resurface anytime in the future. So if you are using this data as part of your encryption, best is to save this to the keychain.

Save the vendorID in the KeyChain, that will persist after deletion or any update.
-(NSString *)getUniqueDeviceIdentifierAsString
{
NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
NSString *strApplicationUUID = [SSKeychain passwordForService:appName account:#"incoding"];
if (strApplicationUUID == nil)
{
strApplicationUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[SSKeychain setPassword:strApplicationUUID forService:appName account:#"incoding"];
}
return strApplicationUUID;
}
Disclaimer: I took the code from some other SO answer a while ago, so
I don't remember whom to praise, but in the end it's not me
#Jonny found the source

I'm confused why you're saving the IFV to NSUserDefaults first and then checking to see if that key exists. I think you should...
1) check NSUserDefaults first to see if the IFV key you created exists
2) if NSUserDefaults key does exist, great do what you need with it, this user's profile already exists
3) if key does not exist, get the IFV and save it to NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults valueForKey:#"IFV"]) {
//this user already exists, do what you need to do next with IFV
}
else{
//this is their first time using the app
NSString *ifvString = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[defaults setValue:ifvString forKey:#"IFV"];
//do what you need to do next with IFV
}

Related

iOS Firebase CrashlyticsKit not setting user ID

I am trying to set a user ID for Firebase Crashlytics reports.
Currently I send a user ID only if it's not sent or is changed (very rare event). And there is no user ID in crash reports.
My code:
+ (void)setCrashlyticsUserData:(User *)user
{
if (user == nil) { return; }
NSString *userIdKey = #"CRASHLYTICS_SENT_USER_ID";
NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults;
NSInteger sentUserId = [userDefaults integerForKey:userIdKey];
if (sentUserId == user.userId) { return; }
[CrashlyticsKit setUserIdentifier:[NSString stringWithFormat:#"%i", user.userId]];
[userDefaults setInteger:user.userId forKey:userIdKey];
}
If this line is commented if (sentUserId == user.userId) { return; } I receive a user ID in crash reports.
Should I call [CrashlyticsKit setUserIdentifier:] every app launch? I can't find any information about it in the documentation.
Custom attributes of Crashlytics (like custom keys or user identifier) works in log-style in per-session basis.
So, you should call setUserIdentifier in each app session as early as possible.
See this link for code example:
https://fabric.io/kits/ios/crashlytics/features
You can use [Crashlytics setUserIdentifier:] to provide an id number, token, or hashed value that uniquely identifies the end-user of your application without disclosing or transmitting any of their personal information. You can also clear the value by setting it to a blank string. This value is displayed right in the Crashlytics dashboard.
[CrashlyticsKit setUserIdentifier:#"123456789"]; //User ID
Or another option is setthe custom key using [CrashlyticsKit setObjectValue:forKey:], See the following example.
[CrashlyticsKit setIntValue:3 forKey:#"userid"];
[CrashlyticsKit setObjectValue:#"logged_in" forKey:#"last_UI_action"];
See this document for more information.
https://docs.fabric.io/apple/crashlytics/enhanced-reports.html

Check if user is connected to his Apple Account, if not, ask him to connect to it

Is it possible to check if the user is connected to his Apple Account ? And if he is not, ask him to connect ?
Yes it is possible to get Apple account (iCloud) status by checking if the ubiquityIdentityToken object in NSFileManager is set. You can use something like this:
Swift 4
var iCloudAvailability: Bool {
return FileManager.default.ubiquityIdentityToken != nil
}
Swift 3
var icloudStatus: Bool {
return NSFileManager.defaultManager().ubiquityIdentityToken != nil ? true : false
}
Value of true would indicate that user is logged in, and false that user is not logged in, so you can present some kind of view to ask user to connect.
From the Apple documentation:
When iCloud is currently available, this property contains an opaque object representing the identity of the current user. If iCloud is unavailable for any reason or there is no logged-in user, the value of this property is nil. Accessing the value of this property is relatively fast so you can check the value at launch time from your app’s main thread.
If you are checking for iCloud services then from CloudKit you can use CKContainer class to get user account status as below
CKContainer *container = [CKContainer defaultContainer];
[container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) {
//Here goes your code to handle the account status
}];
iOS 10.x swift 4.0 Based on the Said Sikira answer
func icloudStatus() -> Bool?{
return FileManager.default.ubiquityIdentityToken != nil ? true : false
}

Keychain returns errSecNotAvailable when using access control

SecItemCopyMatching returns errSecNotAvailable when trying to read a kSecClassIdentity Keychain item on a physical device (iPhone 6S) when the item is saved with kSecAccessControlUserPresence.
When I run the code, the device asks me to authenticate using TouchID. I use my finger and the prompt goes away, but then it takes a relatively long time for SecItemCopyMatching to return, and when it does, it gives errSecNotAvailable.
This is weird, because TouchID works when I use LocalAuthentication (without using Keychain). Retrieving the certificate also works if I save it without the access control attribute. But I want to use kSecAccessControlUserPresence. Any idea why I get the error?
Adding certificate:
- (BOOL)keychainAddIdentity:(SecIdentityRef)identity withLabel:(NSString *)label {
CFErrorRef error = NULL;
SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlUserPresence, &error);
NSLog(#"SecAccessControlCreateWithFlags error: %#", error); // always null
NSDictionary *attributes = #{
(id)kSecAttrLabel: label,
(id)kSecValueRef: (__bridge id)identity,
(id)kSecAttrAccessControl: (__bridge id)sacObject
};
OSStatus status = SecItemAdd((CFDictionaryRef)attributes, NULL);
[self printOSStatus:status]; // errSecSuccess
return status == errSecSuccess;
}
Reading certificate:
- (SecIdentityRef)keychainGetIdentityWithLabel:(NSString *)label userPromptMessage:(NSString *)message {
NSDictionary *query = #{
(id)kSecClass: (id)kSecClassIdentity,
(id)kSecAttrLabel: label,
(id)kSecReturnRef: #YES,
(id)kSecUseOperationPrompt: message
};
SecIdentityRef identity = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&identity);
[self printOSStatus:status]; // errSecNotAvailable
return identity;
}
Test code:
SecIdentityRef identity = [... load certificate file ...];
BOOL certSaved = [self saveCertificate:identity]; // YES
SecIdentityRef cert = [self loadCertificate]; // (null)
So when adding the certificate without sacObject, everything works fine, but with it, I get errSecNotAvailable. Why?
It appears that you're missing the key/value for kSecClass: kSecClassIdentity attribute when saving the identity to the keychain. Without specifying the class on the way in I don't think there's any way to read it later.
To my background, keychain data should be able to use the keychain after 200ms after launch of the app. So, when you want to reach this data? Do you have enough time to fetch this data.
Also keychain data can tie to your TouchID or faceID. But, maybe iphone6s's sensor is not good(we had one formerly, It'd not been working perfectly). To be ensure, you should try to reach this data with normal login.
And I'm sure, apple do not update keychain data documentation when It's changed. I met some scenario related to keychain, and sometimes apple documents has wrong info. (for example wrong keychain info in Apple document)
As a result, your job is not easy, please try above suggestions. If it doesn't work try another way to accomplish your requirements.

NSUserDefaults standardUserDefaults not working with extension

I added App Groups to my app ID in the developer portal and am using that App ID in my provisioning profile. My Product Identifier in Xcode is set to that app ID.
In my app delegate I call this from didFinishLaunchingWithOptions
NSUserDefaults.standardUserDefaults().setObject("hey", forKey: "TEST")
NSUserDefaults.standardUserDefaults().synchronize()
In my keyboard app extension I call this:
if let test = NSUserDefaults.standardUserDefaults().objectForKey("TEST") as? String
{
println(test)
}
This never is true. If I remove the validation test and just print the result the custom keyboard crashes.
EDIT
Have also tried this with same crash result:
App Delegate
var userDefaults = NSUserDefaults(suiteName: "group.jackedgames.myappgroup")
userDefaults.setObject("TEST", forKey: "TEST")
userDefaults.synchronize()
Keyboard Extension
var userDefaults = NSUserDefaults(suiteName: "group.jackedgames.myappgroup")
var test = userDefaults.objectForKey("TEST") as String
NSLog(test)
In the "Capabilities" section of both targets I have the groups enabled with this group ID selected.
What am I missing here?
Set
RequestsOpenAccess = YES;
Settings:
NSUserDefaults * usrInfo = [[NSUserDefaults alloc] initWithSuiteName:#"myKeyboard"];
[usrInfo setObject:theme.icon forKey:#"themeName"]; // This is the new data;
[usrInfo synchronize];
keyboardChange:
NSUserDefaults * usrInfo = [[NSUserDefaults alloc] initWithSuiteName:#"myKeyboard"];
[usrInfo synchronize];
NSString * str = [usrInfo objectForKey:#"themeName"];
Then you can change the keyboard , for example ,change its background
Remove your keyboard from the iOS settings menu and add it again with "allow full access permission". It should work.
If it still doesn't work, check for both containing application and keyboard extension in target configuration in "Capabilities" section in app group that there are everything is fixed.
Just stepped on this. In keyboard extension you need "Full Access" to have access to write something to the app group files. So without "Full Access" you need to use NSUserDefaults.standardUserDefaults
The problem is that when you're changing NSUserDefaults from app group - it is working until you restart the app, because it stores values in memory too. Hard to debug.
I'm saving value to both: app group NSUserDefaults and NSUserDefaults.standardUserDefaults. And when reading - checking both too.

How to detect if a Facebook access token has been refreshed?

in my app I need to detect if an access token has been refreshed so that i can update my API with the new token.
I am having trouble trying to figure out the best way to approach this since the Facebook SDK automatically caches the access token - currently I am comparing the old token (which i save to NSUserDefaults) with the accessToken of the activeSession's accessTokenData.
If the tokens are different I send update my api with the newer token.
here is my code within applicationWillEnterForeground
NSString *newToken = [FBSession activeSession].accessTokenData.accessToken;
NSString *oldToken = [[NSUserDefaults standardUserDefaults]valueForKey:#"accessToken"];
if (oldToken) {
if ([newToken isEqualToString:oldToken]) {
NSLog(#"token was not refreshed");
} else {
NSLog(#"token was refreshed");
[[NSUserDefaults standardUserDefaults]setValue:newToken forKey:#"accessToken"];
/* update API with new token*/
}
} else+
{
//if no token exists, set one with the current token
[[NSUserDefaults standardUserDefaults]setValue:newToken forKey:#"accessToken"];
}
I have to admit it's really hacky, but I cant seem to find a way around it. Is there any better way of approaching it?

Resources