Edit an object in an NSMutableArray saved with NSUserDefaults - ios

i'm getting [__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object when I try to edit an object which I get from NSUserDefault.
I have searched through stackoverflow and tried making the mutable copy after retrieving the data but still getting the error. Here is the current code
I set this in the first view controller
NSMutableArray *settings=[[NSMutableArray alloc]init];
[settings addObject:[#{
#"single":#"Player1",
#"multiplesame":[[NSMutableArray alloc] initWithObjects:#"Player1",#"Player2",#"Player3",#"Player4",#"Player5", nil],
#"multipleDiff":[UIDevice currentDevice].name
}mutableCopy]];
[Reusables storeArrayToDefaults:SETTINGS_DETAILS objectToAdd:settings];
Now in another view controller, I'm trying to change the value of Player1 of single key to a name. For that this is how I retrieve the data.
NSMutableArray *setDetails = [[[NSUserDefaults standardUserDefaults] arrayForKey:SETTINGS_DETAILS] mutableCopy];
//setDetails= [[NSMutableArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] arrayForKey:SETTINGS_DETAILS]] ;
//setDetails = [setDetails mutableCopy];
NSLog(#"Details:%#",setDetails);
The log prints fine
Details:(
{
multipleDiff = "iPhone Simulator";
multiplesame = (
Player1,
Player2,
Player3,
Player4,
Player5
);
single = Player1;
}
)
But now when I try to change the value like this it gets crashed with the above error
[[setDetails objectAtIndex:0] setObject:#"Any Name" forKey:#"single"] ;
For reference this is how I save in NSUserDefaults
+(void)storeArrayToDefaults :(NSString *)keyName objectToAdd:(NSMutableArray *)arrayData
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:arrayData forKey:keyName];
[defaults synchronize];
}

Try this
NSMutableArray *setDetails = [NSMutableArray arrayWithArray:[[[NSUserDefaults standardUserDefaults] arrayForKey:SETTINGS_DETAILS] mutableCopy]];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[setDetails objectAtIndex:0]];
[dict setObject:#"Any Name" forKey:#"single"];
[setDetails replaceObjectAtIndex:0 withObject:dict];

Everything in NSUserDefaults is immutable. You create a mutable copy of the outer container but its contents are still immutable. So, to edit them, you need to take a mutable copy, update that and then reinsert (and re-add to defaults).
[[setDetails objectAtIndex:0] setObject:#"Any Name" forKey:#"single"];
changes to
NSMutableDictionary *dict = [[setDetails objectAtIndex:0] mutableCopy];
[dict setObject:#"Any Name" forKey:#"single"];
[setDetails replaceObjectAtIndex:0 withObject:dict];

Related

copy NSUserDefaults array and edit the array

I am getting crash when i am replacing the value in NSMutableArray and the message is
"Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '-[__NSCFDictionary
setObject:forKey:]: mutating method sent to immutable object'"
What I am doing is first store an array in NSUserDefaults and then get the array . After that i am editing the copy of that array .
NSMutableArray *getArr = [[NSUserDefaults standardUserDefaults] valueForKey:#"List"];
NSMutableArray *newArray = [[NSMutableArray alloc]initWithArray:getArr copyItems:YES];
And Now I am replacing the value of new Array at particular index
for(int i = 0;i<newArray.count;i++)
{
NSDictionary *dict = [newArray objectAtIndex:i];
dict["key"] = "NewValue"
[newArray replaceObjectAtIndex:i withObject:d];
}
The problem is that the stored dictionary object is immutable , so you need to init a mutable one with it's content , do the job then save it again see this example
NSMutableArray*old= [NSMutableArray new];
[old addObject:#{#"key":#"value"}];
[[NSUserDefaults standardUserDefaults] setObject:old forKey:#"www"];
NSMutableArray*edited = [[[NSUserDefaults standardUserDefaults] objectForKey:#"www"] mutableCopy];
NSMutableDictionary*dic = [[NSMutableDictionary alloc] initWithDictionary:[edited objectAtIndex:0]];
dic[#"key"] = #"value2";
[edited replaceObjectAtIndex:0 withObject:dic];
[[NSUserDefaults standardUserDefaults] setObject:edited forKey:#"www"];
NSMutableArray*vvv = [[NSUserDefaults standardUserDefaults] objectForKey:#"www"];
NSLog(#"%#",vvv); /// key:value2
The problem is that you have only array copied as mutable. But dictionaries inside are still immutable objects. You can try making a deep mutable copy but for your case you can just do the following
for(int i = 0;i<newArray.count;i++)
{
//Just copy the dict as mutable and then change the value
NSDictionary *dict = [[newArray objectAtIndex:i] mutableCopy];
dict["key"] = "NewValue"
[newArray replaceObjectAtIndex:i withObject:dict];
}

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

change object get from NSUserDefaults

I’d like to change object get from NSUserDefaults.
But when executing following code, the app crash with error message:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object’
I have this code.
NSUserDefaults* pref = [NSUserDefaults standardUserDefaults];
NSMutableArray* data = [[NSMutableArray alloc]init];
data = [[pref objectForKey:#"Data"] mutableCopy];
for (NSArray* array in data) { //make object mutable
[array mutableCopy];
for (NSDictionary* dic in array) {
[dic mutableCopy];
}
}
[data[0][1] setObject:#"YES" forKey:#"isChecked"]; //for example
[pref setObject: data forKey: #"Data"];
[pref synchronize];
The structure of data:
NSMutableArray* arr1 = [#[[#{#"name":#"abc", #"isChecked":#"YES"} mutableCopy], [#{#"name":#"def", #"isChecked":#"NO"} mutableCopy]] mutableCopy];
NSMutableArray* arr2 = [#[[#{#"name":#"ghi", #"isChecked":#"NO"} mutableCopy], [#{#"name":#"jkl", #"isChecked":#"YES"} mutableCopy]] mutableCopy];
NSMutableArray* data = [#[arr1, arr2] mutableCopy];
[userDefaults setObject: data forKey: #"Data"];
How do I fix it to change object in NSArray or NSDictionary get from NSUserDefaults?
You have the right idea - the objects returned from NSUserDefaults are immutable, and you need to use mutableCopy to get a mutable copy. However, your implementation is flawed.
mutableCopy returns a new, mutable copy of the object. It does not make the existing object mutable. You are simply ignoring the mutable copy that you are creating.
As you want to mutate the data in your arrays, you can't use enumeration or you will get an error that the object was mutated while enumerating, so you will need to access the arrays by index -
NSUserDefaults* pref = [NSUserDefaults standardUserDefaults];
NSMutableArray* data = [[pref objectForKey:#"Data"] mutableCopy];
for (int i=0;i<data.count;i++) {
NSMutableArray *mArray = [data[i] mutableCopy];
for (int j=0;j<mArray.count;j++) {
NSMutableDictionary *mDictionary=[mArray[j] mutableCopy];
mDictionary[#"SomeKey"]=#"SomeValue";
mArray[j]=mDictionary;
}
data[i]=mArray;
}
[userDefaults setObject: data forKey: #"Data"];
[array mutableCopy] will create a mutable copy of the array and returns it. You are not assigning it to the variable again. This will resolve your issue i guess.
array = [array mutableCopy];
similarly
dic = [dic mutableCopy];

Attempt to insert non-property value Objective C

Hi l am trying to create a fourates lists from an restaurants Object, my application has a list of different restaurants, l want the ability for users to add favourate restaurants, and this code is not working
- (IBAction)toggleFav:(id)sender {
Restaurant *resto = [self restaure];
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setObject:resto.price forKey:#"restoPrice"];
[dic setObject:resto.restaurantId forKey:#"restaurantId"];
[dic setObject:resto.restoAbout forKey:#"restoAbout"];
[dic setObject:resto.restoAddress forKey:#"restoAddress"];
[dic setObject:resto.restoBeverages forKey:#"restoBeverages"];
[dic setObject:resto.restoCategory forKey:#"restoCategory"];
[dic setObject:resto.restoEmail forKey:#"restoEmail"];
[dic setObject:resto.restoLogo forKey:#"restoLogo"];
[dic setObject:resto.restoName forKey:#"restoName"];
[dic setObject:resto.restoPhone forKey:#"restoPhone"];
[dic setObject:resto.restoCity forKey:#"restoCity"];
NSArray *dicArray = [dic allKeys];
if([sender isSelected]){
//...
[sender setSelected:NO];
NSMutableArray *array = [[[NSUserDefaults standardUserDefaults] objectForKey:#"restoName"] mutableCopy];
[array removeObject:dicArray];
[[NSUserDefaults standardUserDefaults] setObject:array forKey:#"restoName"];
} else {
//...
[sender setSelected:YES];
NSMutableArray *array = [[[NSUserDefaults standardUserDefaults] objectForKey:#"restoName"] mutableCopy];
[array addObject:dicArray];
[[NSUserDefaults standardUserDefaults] setObject:array forKey:#"restoName"];
[[NSUserDefaults standardUserDefaults] synchronize];
//NSLog(#"%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"restoName"]);
}
}
' of class '__NSCFArray'. Note that dictionaries and arrays in property lists must also contain only property values.
If you don't want to bother about ensuring all your dictionary values are the property list types, then you can simply convert the dictionary into NSData,
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
[def setObject:[NSKeyedArchiver archivedDataWithRootObject:self.myDictionary] forKey:#"MyData"];
[def synchronize];
And to get back into a dictionary from sandbox:
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
NSData *data = [def objectForKey:#"MyData"];
NSDictionary *retrievedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data];
self.myDictionary = [[NSDictionary alloc] initWithDictionary:retrievedDictionary];
Hope this helps someone. :D
ADDITIONAL NOTE: If you are getting a mutable dictionary, or mutable array, then remember to use "mutableCopy" method. Otherwise you can't add or delete objects from your retrieved dictionary/array. For example:
NSMutableDictionary *retrievedDictionary =
[[NSKeyedUnarchiver unarchiveObjectWithData:data] mutableCopy];
Or
NSMutableArray *retrievedArray =
[[NSKeyedUnarchiver unarchiveObjectWithData:data] mutableCopy];
You can only store property list types (array, data, string, number, date, dictionary) or urls in NSUserDefaults. You'll need to convert your model object to those.

MutableDictionary turns immutable

In my first function i create an NSMutableDictionary and saves it in an array.
NSMutableArray* tempPlayersArray = [NSMutableArray arrayWithArray: [[NSUserDefaults standardUserDefaults] arrayForKey: #"kgpsTempArray"]];
NSMutableDictionary *tempPlayerDictArray = [[NSMutableDictionary alloc] init];
if (!(userDeviceName)) {
[tempPlayerDictArray setValue:userDeviceName forKey:#"DeviceName"];
}else{
[tempPlayerDictArray setValue:#"empty" forKey:#"DeviceName"];
}
[tempPlayersArray addObject:tempPlayerDictArray];
[defaults setObject:tempPlayersArray forKey:#"kgpsTempArray"];
[defaults synchronize];
In my second function i get it as NSCFDictionary - which is not mutable.
NSMutableArray* tempPlayersArray = [NSMutableArray arrayWithArray: [[NSUserDefaults standardUserDefaults] arrayForKey: #"kgpsTempArray"]];
NSMutableDictionary *dictionaryForSearching = [[NSMutableDictionary alloc] init];
NSLog(#"%#",[dictionaryForSearching class]);
dictionaryForSearching = [tempPlayersArray objectAtIndex:index];
NSLog(#"%#",[[tempPlayersArray objectAtIndex:index] class]);
NSLog(#"%#",[dictionaryForSearching class]);
The first log shows "NSDictionaryM".
The second log shows "NSCFDictionary".
And the third shows "NSCFDictionary" as well...
Can anyone explain me why? And how to fix it?
NSUserDefaults works with inmutable objects, so that's the reason that when you return your dictionary it's changed.
You can try this:
dictionaryForSearching = [[NSMutableDictionary alloc] initWithDictionary:[tempPlayersArray objectAtIndex:index]];
Yes, NSUserDefaults is free to copy, persist, and deserialize as it likes. Assume it does not return mutable objects. If you need a mutable object, make a mutable copy.
Every thing depends from here:
NSMutableArray* tempPlayersArray = [NSMutableArray arrayWithArray: [[NSUserDefaults standardUserDefaults] arrayForKey: #"kgpsTempArray"]];
Reading NSUserDefaults always give you Immutable object.

Resources