change object get from NSUserDefaults - ios

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

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

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

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 - Inserting array as object not working

Here is my code:
NSMutableDictionary *newThoughtDict = [[NSMutableDictionary alloc] init];
[newThoughtDict setObject:self.triggerOne.text forKey:#"thought"];
[newThoughtDict setObject:self.date forKey:#"date"];
NSDate *currentDate = [[NSDate alloc] initWithTimeIntervalSince1970:NSTimeIntervalSince1970];
[newThoughtDict setObject:currentDate forKey:#"date"];
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *arrayData = [currentDefaults objectForKey:#"thoughtArray"];
if (arrayData != nil)
{
NSMutableArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData:arrayData];
[savedArray insertObject:newThoughtDict atIndex:0]; //<---error being thrown here
[currentDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:savedArray] forKey:#"thoughtArray"];
[currentDefaults synchronize];
The error being thrown is: -[__NSArrayI insertObject:atIndex:]: unrecognized selector sent to instance 0x170200150
It seems like the problem is when I insert a dictionary into the array, but the code for doing that is correct so I wonder if there is a problem in how I retrieve the array from NSUserDefaults..
Also, when I test the app, it should return "nil" because I have yet to actually create the NSUserDefault object and key..
Thank you.
NSUserDefaults always returns immutable objects, even if you set a mutable one. You must make a mutable copy if you want to change something returned from defaults.

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

Resources