iOS 8 freezes at updating UserDefaults object - ios

I have a queue of objects stored to the NSUserDefaults. When I need to add objects to it, I call the following method:
+ (void)addCodeToQueue:(Code *)code {
// Note: userDefaults is a static, initialized variable
NSDictionary *codeModel = [self generateCodeModelWith:code];
// Read array from UserDefaults, or create one if nil
NSMutableArray *codeQueue = [userDefaults mutableArrayValueForKey:#"CodeQueue"] ? : [[NSMutableArray alloc] init];
// Add code model
[codeQueue addObject:codeModel];
// Add/replace array & sync
[userDefaults setObject:codeQueue forKey:#"CodeQueue"]; // App freezes here if uncommented
[userDefaults synchronize];
}
My app freezes when I call setObject:forKey. If I add a breakpoint to that line, the continue running, it works. If I don't break, it freezes.
This started happening after I updated Xcode to version 8 and started using the new SDK.
Any hints on this?

I had this problem in several apps as well -- it appears that by returning the mutable array via mutableArrayValueForKey, you can get stuck in a mutex lock. For my code, I swapped this out by:
Getting an immutable array as NSArray *arrSource = [defaults arrayForKey:strKey];
Copying the data into a mutable array:
NSMutableArray *arrMutable = [NSMutableArray arrayWithArray:arrSouce];
Setting my values in arrMutable...
Storing the "modified" array back into the defaults:
[defaults setObject:arrMutable forKey:strKey];
... for me, at least, this has fixed the mutex lock issue.

After my experiment, I have found the minimum code required to generate a mutex lock.
The code is as follows.
NSMutableArray *videoArray = [[NSUserDefaults standardUserDefaults] mutableArrayValueForKey:VIDEOKEY];
[videoArray addObject:item];
[[NSUserDefaults standardUserDefaults] setObject:videoArray forKey:VIDEOKEY];
There are 3 points to pay attention to:
If I delete [videoArray addObject:item];, the app will crash.
The mutex lock is occurring in [[NSUserDefaults standardUserDefaults] setObject:videoArray forKey:VIDEOKEY];.
If I make a breakpoint at [videoArray addObject:item];, the mutex lock is still exist. If I make it at [[NSUserDefaults standardUserDefaults] setObject:videoArray forKey:VIDEOKEY];, the mutex lock will not exist.
It can be inferred that the mutex lock is result by [NSKeyValueSlowMutableArray addObject:]; and [NSUserDefaults setObject:forKey:]
By the way, my solution to solve it is as follows:
NSMutableArray *mutableVideoArray;
NSArray *videoArray = [[NSUserDefaults standardUserDefaults] arrayForKey:VIDEOKEY];
if (videoArray)
mutableVideoArray = [videoArray mutableCopy];
else
mutableVideoArray = [NSMutableArray array];
if (![mutableVideoArray containsObject:item])
{
[mutableVideoArray addObject:item];
[[NSUserDefaults standardUserDefaults] setObject:mutableVideoArray forKey:VIDEOKEY];
}

Related

<__NSCFDictionary: 0x1557f400> was mutated while being enumerated.'

I have ios 7 application that is running on iphone 4. I have a weird problem, where application crashes inside for loop, because of the error in the title. I checked on SO and it says that error occurs when you change object over which you are iterating.
So I copied both variables that I use to temp variables but problem still occurs.
Problem happen when first iteration is finished.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary * badges = [defaults objectForKey:#"badges"];
NSMutableDictionary *newBadges = badges;
for(NSString* key in badges)
{
NSDictionary* badge = [badges objectForKey:key];
if([[badge objectForKey:#"achived"] isEqual: #"NO"])
{
if([self checkBadgeCondition:badge])
{
NSMutableDictionary *tempBadge = [badge mutableCopy];
[self showAlertBadge:badge];
[tempBadge setObject:#"YES" forKey:#"achived"];
[newBadges setObject:tempBadge forKey:[tempBadge objectForKey:#"name"]];
}
}
}
newBadges = badges
This isn't a copy, it's just another reference to the same thing. You also should expect a dictionary (or array) coming out of user defaults to be mutable. So, make a mutable copy of it here
newBadges = [badges mutableCopy]

NSUserDefaults save two arrays leading to crash

Recently I was studying NSUserDefaults, then made a demo as follows:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *activity_array = [NSMutableArray array];
NSMutableArray *movie_array = [NSMutableArray array];
[defaults setObject:activity_array forKey:#"activity"];
[defaults setObject:movie_array forKey:#"movie"];
[defaults synchronize];
Then I tried writing the following which I will be calling "code2" for the duration of this post:
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSMutableArray *array = [userDefault objectForKey:#"activity"];
[array addObject:#"123"];
the demo still works.
However the demo crashes when I replace "code2" with the following code:
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSMutableArray *array = [userDefault objectForKey:#"movie"];
[array addObject:#"123"];
As you can see, the difference is the key.
Why does this crash?
NSUserDefaults can store NSMutableArrays, but will turn them into immutable arrays.
Same goes for NSDictionary and NSMutableDictionary.
That means if you want to add an object to an array that you just extracted from the NSUserDefaults, you will have to first make it mutable first, using -mutableCopy for example, or -initWithArray.
NSArray *array = [userDefault objectForKey:#"movie"];
//This works
NSMutableArray *arr = [NSMutableArray alloc]initWithArray:array];
//This works too and is more commonly used.
NSMutableArray *arr = [array mutableCopy];
You can now modify the array arr without any trouble, and you will be able to save it just like you did before. If you retrieve it afterwards, it will be the modified array. But be careful, arrays and dictionaries are always immutable when taken from NSUserDefaults. You will have to do that "trick" everytime you want to modify an array or dictionary from the NSUserDefaults.
EDIT : after testing your code, my only assumption is that your crash-free array is simply nil when you retrieve it. Debug with breakpoints to verify this but I'm close to 101% sure.
EDIT2 : trojanfoe got that faster than I did !
As others have pointed-out the arrays you get back from NSUserDefaults are immutable, so an exception will be thrown when calling addObject: on them, however that won't occur if the array is nil as calling methods (sending messages) to objects that are nil are silently ignored.
Therefore I believe your code2 works as the object #"activity" doesn't exist in the user defaults, while #"movie" does.
Arrays and dictionaries returned from NSUserDefaults are always immutable, even if the one you set was mutable. You'll have to call -mutableCopy.
Try this:
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSMutableArray *array = [[userDefault objectForKey:#"movie"]mutableCopy];
The object from userDefault is not mutable.. Try this
NSMutableArray *arr = (NSMutableArray *)[userDefault objectForKey:#"activity"];
or if you like use id and check its class first to prevent crashing:
id variableName = [userDefault objectForKey:#"activity"];
if ([[variableName class] isEqual:[NSArray class]])
{
NSMutableArray *arr = [[NSMutableArray alloc] initWithArray:(NSArray *)variableName];
NSMutableArray *arr = [(NSArray *)variableName mutableCopy];
}
else if ([[variableName class] isEqual:[NSNull class]])
NSLog(#"no object with key:activity");
else
NSLog(#"not array");
//Happy coding.. :)

NSUserdefaults multiple data types for one key

After reading from various answers I have come to know that NSUserDefaults can save multiple datatypes for one key. But what I cannot find is if
[NSUserDefaults standardUserDefaults] removeObjectForKey:"someKey"];
removes all objects of all data types associated with that key?
You cannot store different kind of objects for one key.
If you set an object for a key it will erase the old one.
But, if your are searching for a way to store multiple data for one key, you can store a NSDictionary.
Ex :
MyObject *obj = [[MyObject alloc] init];
NSString *otherType = #"mystring";
NSDictionary *multipleData = #{ #"key1" : obj , #"key2" : otherType}
[[NSUserDefaults standardUserDefaults] setObject: multipleData forKey:#"multipleData"];
[[NSUserDefaults standardUserDefaults] synchronize];
And if you want to remove it :
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"multipleData"];
[[NSUserDefaults standardUserDefaults] synchronize];
Yes it does.
Your data may be anything an array or dictionary or simple int. This command will remove that data.
As iPatel suggested. You need to call:
[[NSUserDefaults standardUserDefaults] synchronize];
After adding or deleting any data. Hope this helps.. :)
You cannot store multiple objects under one key. NSUserDefaults acts just like a NSDictionary. When you set an object for a specific key you overwrite the old object. So removeObjectForKey: just removes one object/value; the one you had stored under that key.
Do you call
[[NSUserDefaults standardUserDefaults] synchronize];
after delete all the data of the key and also might be you can not store multiple data on single key, it's return new one that inserted to last. ?
Read official documentation of removeObjectForKey of NSUserDefaults.
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSString *yourDomain = [[NSBundle mainBundle] bundleIdentifier];
[userDefault removePersistentDomainForName:yourDomain];
Here. if u want to reset.

How to add objects into NSMutableArray without deleting the existing ones?

Im having trouble with my nsmutablearray in NSUserdefaults, this array every time I relaunch the app erases the objects that already are there and put the new ones, so I need help to prevent these to happen, Thanks and this is my code;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (!self.tasks) self.tasks = [NSMutableArray new];
[self.tasks addObject:textField.text];
[userDefaults setObject:self.tasks forKey:#"tasks"];
//[userDefaults synchronize];
NSLog(#"tasks:%#", [[NSUserDefaults standardUserDefaults]objectForKey:#"tasks"]);
NSLog(#"number of tasks:%d", self.tasks.count);
And Im reading it in a tableview this way:
cell.taskTitle.text = (self.TasksArray)[indexPath.row];
Thanks!
You are missing a line of code there:
self.tasks = [[NSMutableArray alloc] initWithArray:[userDefaults objectForKey:#"tasks"];
As far as I can tell, you're not initially setting your ".tasks" property, so adding that bit may fix the problem.
You need to get it from user defaults first as rmaddy suggests. Your nil check will then create a new array if its never been created/saved before.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
self.tasks = [[userDefaults objectForKey:#"tasks"] mutableCopy];
if (!self.tasks) self.tasks = [NSMutableArray new];
[self.tasks addObject:textField.text];
[userDefaults setObject:self.tasks forKey:#"tasks"];
//[userDefaults synchronize];
NSLog(#"tasks:%#", [[NSUserDefaults standardUserDefaults]objectForKey:#"tasks"]);
NSLog(#"number of tasks:%d", self.tasks.count);
I see you never call [[NSUserDefaults standardUserDefaults] synchronize];
Once you finish with changes of your object you should call it since it saves your changes to disk.

NSMutableArray adding elements get overwritten in iPhone

I'm trying to add elements to an NSMutableArray whenever a user selects a country. But each time I use [myarray setobject:#""];, it's adding the new value, overwriting my old value. I want this array as I'm using it in:
[[NSUserDefaults standardUserDefaults]setObject:(NSMutableArray *)selectedCountriesByUser forKey:#"userSelection"];
[[NSUserDefaults standardUserDefaults]synchronize];
I want an array which maintains the list of countries selected by the user even after the application is closed.
What should I do?
setObject replace all objects in array
for example, get value from NSUserDefault:
NSMutableArray *myMutableArray = [NSMutableArray arrayWithArray:[[NSUserDefault standardUserDefault] objectForKey:"userSelection"]];
you should use [myMutableArray addObject:"aCountry"]; without overwriting, but adding only
and after
[[NSUserDefaults standardUserDefaults]setObject:myMutableArray forKey:#"userSelection"];
[[NSUserDefaults standardUserDefaults]synchronize];
EDIT:
-(void) viewDidLoad {
//your selectedCountriesByUser
myMutableArray = [NSMutableArray arrayWithArray:[[NSUserDefault standardUserDefault] objectForKey:"userSelection"]];
}
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//add object to array
[myMutableArray adObject:"yourObj"];
[[NSUserDefaults standardUserDefaults]setObject:myMutableArray forKey:#"userSelection"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
You're asking two different things. First, -setObject: is not a NS(Mutable)Array method. You are probably looking for the -addObject: method. So, to add an object to your NSMutableArray, you need to do:
[myMutableArray addObject:yourObject]
//Remember that `-addObject` is present only in NSMutableArray, not in NSArray
The second thing you are trying to achieve is to store the array in NSUserDefaults. to do so, after you add the object to the array you want, you should be fine do to so:
[[NSUserDefaults standardUserDefaults] setObject:myMutableArray forKey:#"userSelection"];
[[NSUserDefaults standardUserDefaults] synchronize];
// ARRAY DECLARATION AND ASSIGNMENT OF VALUES TO IT
NSMutableArray * selectedCountriesByUserArray=[[NSMutableArray alloc] init];
[selectedCountriesByUserArray addObject:#"value1"];
[selectedCountriesByUserArray addObject:#"value2"];
[selectedCountriesByUserArray addObject:#"value3"];
// STORING AN ARRAY WITH THE KEY "userSelection" USING NSUSERDEFAULTS
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:selectedCountriesByUserArray forKey:#"userSelection"];
[defaults synchronize];

Resources