NSUserDefaults - Inserting array as object not working - ios

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.

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

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

Adding new object to retrieved NSMutableArray - returns NSInternalInconsistencyException

I keep getting this error when I try to add an object to a retrieved NSMutableArray.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
Retrieve:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *arrayOfTitles = [userDefaults objectForKey:#"mainArraySaveData"];
NSMutableArray *arrayOfSubjects = [userDefaults objectForKey:#"subjectArraySaveData"];
NSMutableArray *arrayOfDates = [userDefaults objectForKey:#"dateArraySaveData"];
_mutableArray=arrayOfTitles;
_subjectArray=arrayOfSubjects;
_dateArray=arrayOfDates;
[_tableView reloadData];
Save:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
_mutableArray = [[NSMutableArray alloc] initWithArray:[userDefaults objectForKey:#"mainArraySaveData"]];
[userDefaults setObject:_mutableArray forKey:#"mainArraySaveData"];
[userDefaults synchronize];
_dateArray = [[NSMutableArray alloc] initWithArray: [userDefaults objectForKey:#"dateArraySaveData"]];
[userDefaults setObject:_dateArray forKey:#"dateArraySaveData"];
[userDefaults synchronize];
_subjectArray = [[NSMutableArray alloc] initWithArray: [userDefaults objectForKey:#"subjectArraySaveData"]];
[userDefaults setObject:_subjectArray forKey:#"subjectArraySaveData"];
[userDefaults synchronize];
I'm confused as I thought this was designed to return an NSMutableArray, but it says not - NSArray. What's my issue??
Thanks, SebOH
I'm confused as I thought this was designed to return an NSMutableArray, but it says not - NSArray. What's my issue?
The documentation of the NSUserDefaults talks specifically about this issue in the "special considerations" section of the objectForKey: method:
Special Considerations
The returned object is immutable, even if the value you originally set was mutable.
Fixing this problem is easy - use mutableCopy:, initWithArray: or arrayWithArray: method to make mutable copies:
_mutableArray=[arrayOfTitles mutableCopy]; // You can do this...
_subjectArray=[NSMutableArray arrayWithArray:arrayOfSubjects]; // ...or this
_dateArray=[[NSMutableArray alloc] initWithArray:arrayOfDates]; // ...or this.
User defaults returns immutable objects so you need to call mutableCopy on each before you can modify them.
When you define a variable as NSMutableArray * it is your responsibility to ensure that the instance you store there is of the correct type. The compiler will only tell you that you're wrong if it can tell. In this case the method returns id as you are requesting 'whatever object type exists for this key' from user defaults.

iOS - get NSDictionary from NSUserDefaults :Attempted to dereference an invalid ObjC Object or send it an unrecognized selector

I try to retrieve NSMutableArray from NSUserDefaults that have been saved.
I store the NSMutableArray:
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray* mySavedTremps = [[defaults objectForKey:UD_MY_TREMPS] mutableCopy];
if (!mySavedTremps)
mySavedTremps =[[NSMutableArray alloc] init];
NSMutableDictionary* trempDict = NSMutableDictionary* trempDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"please", #"help", #"me" #"!", nil]
[trempDict setValue:trempId forKey:#"trempId"];
[mySavedTremps insertObject:trempDict atIndex:0];
[defaults setObject:mySavedTremps forKey:UD_MY_TREMPS];
[defaults synchronize];
And try to retrieve the NSMutableArray:
NSMutableArray* myTrempsArray = [NSMutableArray arrayWithArray:[defaults objectForKey:UD_MY_TREMPS]];
for (Tremp* tremp in myTrempsArray) {
if([tremp.trempId isEqualToString:#"1234"]) {
[myTrempsArray removeObject:tremp];
break;
}
}
But, when I access tremp (param in the for loop) like:
tremp.trempId
I get this error:
error: Execution was interrupted, reason: Attempted to dereference an invalid ObjC Object or send it an unrecognized selector.
The process has been returned to the state before expression evaluation.
When you save your Tremp object to the defaults, you are actually saving it as a dictionary.
But when you read it out, your code assumes you have an array of Tremp objects.
You want something like:
for (NSDictionary *trempDict in myTrempsArray) {
Tremp *tremp = ... // add code here to create a Tremp from the dictionary
if([tremp.trempId isEqualToString:#"1234"]) {
[myTrempsArray removeObject:tremp];
break;
}
}
BTW - this code will crash. You can't modify an array that you are fast-enumerating through. Change the loop to be a standard for loop but go through the loop in reverse.
Also, when you save the data, replace the call to setValue:forKey: to setObject:forKey:.

Resources