copy NSUserDefaults array and edit the array - ios

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];
}

Related

Unable to make mutable array iOS

I am new to iOS,
I am storing array of dictionary in NSUserDefaults,
What I want to do is now edit the array of dictionary stored in NSUserDefaults.
Here is the code I have written but it crashes on this line
[array[0] setObject:updateMonth forKey:#"Month"];
and says:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary
setObject:forKey:]: mutating method sent to immutable object
Code snippet:
Storing:
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"MONTHLY_BUDGET"];
and my data is
(
{
Amount = 1;
Month = "July 2017";
isMonthlySet = 1;
}
)
Retriving:
array = [[[NSUserDefaults standardUserDefaults] objectForKey:#"MONTHLY_BUDGET"] mutableCopy];
NSString *updateMonth = #"ABCD";
[array[0] setObject:updateMonth forKey:#"Month"];
Please help and why it is not working?
Try this code
[[array[0] mutableCopy] setObject:updateMonth forKey:#"Month"];
You are doing correct with array
array = [[[NSUserDefaults standardUserDefaults] objectForKey:#"MONTHLY_BUDGET"] mutableCopy];
But array contains NSDictionary not NSMutableDictionary so you required to use mutableCopy
Hope it is clear to you
EDIT
You should use readable code make you easy to change in future
use below code
NSMutableArray *array = [[[NSUserDefaults standardUserDefaults] objectForKey:#"MONTHLY_BUDGET"] mutableCopy];
NSMutableDictionary *dictData = [array[0] mutableCopy]
// Set value
[dictData setObject:updateMonth forKey:#"Month"];
// Assign to array back
[array replaceObjectAtIndex:0 withObject:dictData]
Change UserDefault
[[NSUserDefaults standardUserDefaults] setObject:[array copy] forKey:#"MONTHLY_BUDGET"];

Can't delete object in array

I'm setting data in NSUserDefault to one call to other class like :
kPref = [NSUserDefaults standardUserDefaults]
[kPref setObject:dicOrder forKey:SEND_DATA];
And retrieving like :
NSMutableDictionary *temp = [[kPref dictionaryForKey:SEND_DATA] mutableCopy];
[arrayOrder addObject:temp];
Now i can not delete any object in temp from arrayOrder.
How can i access this NSMutableDictionary that coming from kpref
OR how to delete any object in dictionary.
EDIT
im removing it like :
[[[[[arrayOrder objectAtIndex:0] valueForKey:ORDER_Choice] valueForKey:str] mutableCopy] removeObjectAtIndex:0];
and it return me same array.
Thanks all , I got the solution :
Using archivedDataWithRootObject:dicOrder :
[kPref setObject:[NSKeyedArchiver archivedDataWithRootObject:dicOrder] forKey:SEND_DATA];
And unarchiveObjectWithData:dataRepresentingSavedArray like :
NSData *dataRepresentingSavedArray = [kPref objectForKey:SEND_DATA];
NSMutableDictionary *newDict = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];

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];

Edit an object in an NSMutableArray saved with NSUserDefaults

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];

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