MutableDictionary turns immutable - ios

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.

Related

NSMutableArray is affecting the another NSMutableArray?

I have two NSMutableArrays, both getting the same data. My problem is that when I assign the same data to both the mutable arrays from different dictionaries and perform operations on one NSMutableArray, it is affecting both arrays.
When I perform operations like replaceObjectAtIndex:WithObject:, the first time, the array is not affected but when the second replace is called both arrays have the replaced value. I think it is a reference issue.
Does anyone have a solution to this?
Name of the NSMutableArrays is helper.urlsRecording and helper.holdingArr.
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSMutableDictionary *dict2 = [[NSMutableDictionary alloc] init];
[dict setValue:outputFileURL forKey:#"URL"];
[dict setValue:#"1" forKey:#"index"];
[dict2 setValue:outputFileURL forKey:#"URL"];
[dict2 setValue:#"1" forKey:#"index"];
[helper.urlsRecording addObject:dict];
[helper.holdingArr addObject:dict2];
[helper.urlsRecording replaceObjectAtIndex:button.tag withObject:urlAr];//When this called second time, both the arrays is effected(helper.urlsRecording as well as helper.holdingArr).
How can I prevent the copying of the reference to another array?
Button Click:
if([button isSelected] == NO){
NSLog(#"Url Recording : %#",helper.urlsRecording);
[[helper.urlsRecording objectAtIndex:button.tag] removeObjectForKey:#"URL"];
button.selected = YES;
NSLog(#"Url Recording : %#",helper.urlsRecording);
}
else{
[helper.urlsRecording replaceObjectAtIndex:button.tag withObject:[helper.holdingArr objectAtIndex:button.tag]];
button.selected = NO;
NSLog(#"Url Recording : %#",helper.urlsRecording);
}
Note: NSMutableArray is defined globally in a class to access.
This is because your instance values are same for both dictionary.
So First create one mutableDictionary like below
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:outputFileURL forKey:#"URL"];
[dict setValue:#"1" forKey:#"index"];
And create second dictionary through mutableCopy, so instance will be different for both.
NSMutableDictionary *dict2 = [dict mutableCopy];
After that you can add them in to NSMutableArray and update accordingly.
Take a copy/mutableCopy dictionary & then add object to the MutableArray
[helper.urlsRecording addObject:[dict copy]];
[helper.holdingArr addObject:[dict2 copy]];

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

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

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.

Add NSArray To NSMutableArray

Thanks for you time and reading this. What I'm trying to do is figure out why this NSLog is telling me the NSArray is always null, no matter what. I'm thinking that the problem is that I'm initiating the NSMutableArray wrong. Could you perhaps take a look and decide whether or not I did it right, and if at all possible give me a way to pass the array into the NSMutableArray?
Thanks!
//Get Defaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *favoriteArray = [[defaults objectForKey:#"favorites"] copy];
//Declares Mutable Array
self.favorites = [[NSMutableArray alloc] initWithObjects:favoriteArray, nil];
NSLog(#"array: %#", favorites);
UPDATE: I figured it out. It turns out you have to declare it with initWithArray rather than trying to add it as an object
Solution:
- (void)viewDidLoad {
//Get Defaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *favoriteArray = [[defaults objectForKey:#"favorites"] copy];
//Declares Mutable Array
self.favorites = [[NSMutableArray alloc] initWithArray:favoriteArray];
[super viewDidLoad];
}
The way to do this is using the arrayWithArray and here is how you do it:
myNSMutableArray = [NSMutableArray arrayWithArray:myArray];
Do you ever set an object in your user defaults for the "favorites" key?

Resources