Problems with NSMutableDictionary and NSMutableArray ( Mutating Error - but why? ) [duplicate] - ios

The following code is returning an exception with the following error message "mutating method sent to immutable object" when attempting to removeObjectForKey
NSMutableDictionary * storedIpDictionary = (NSMutableDictionary*)[[NSUserDefaults standardUserDefaults] dictionaryForKey:#"dictDeviceIp"];
NSString *key = self.currentDeviceNameText.text;
NSString *ipAddressTemp = [storedIpDictionary objectForKey:key];
[storedIpDictionary removeObjectForKey:key]; <----Crashes here
storedIpDictionary[key] = ipAddressTemp;
Not sure what the issue is, perhaps it is due to retrieving the dictionary from a NSUserDefaults.
However the following code works without any issues.
NSMutableDictionary * storedIpDictionary = (NSMutableDictionary*)[[NSUserDefaults standardUserDefaults] dictionaryForKey:#"dictDeviceIp"];
[storedIpDictionary removeAllObjects];

NSUserDefaults returns immutable objects, even if you put in mutable ones. You must call -mutableCopy on the returned value to get a mutable collection.

You cant just cast an NSDictionary to NSMutableDictinary thats not at all how casting works.
to remove a key from NSUserDefualts call removeObjectForKey on the NSUserDefaults instance itself.
if you really do want a dictionary for some other reason, then you must make a mutableCopy from the dictionary obtained by dictionaryForKey.

This is the code that eventually worked, I used some of the details provided from others above, but none had it completely explained.
- (void)cleanDictionary
{
NSMutableDictionary * storedIpDictionary = [[[NSUserDefaults standardUserDefaults] objectForKey: #"dictDeviceIp"] mutableCopy];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"dictDeviceIp"];
NSString *oldKey = self.currentDeviceNameText.text;
NSString *newKey = self.deviceNameChangeText.text;
NSString *ipAddressTemp = [storedIpDictionary objectForKey:oldKey];
// Make some change to the structure
[storedIpDictionary removeObjectForKey:oldKey]; // Remove object
storedIpDictionary[newKey] = ipAddressTemp; // Add object with new key
// Add it the whole thing back into NSUserDefaults
[[NSUserDefaults standardUserDefaults] setObject:storedIpDictionary forKey:#"dictDeviceIp"];
// Synchronize to ensure it's saved
[[NSUserDefaults standardUserDefaults] synchronize];
}

if you have on error NSMutableDictionary: mutating method sent to immutable object in Swift, make this step:
This is because you have assigned a NSUserDefault to NSMutableArray, when you take something NSUserDefault it returns you a NSArray not a NSMutableArray, so in this case you have to use a NSMutableArray Auxiliary .
see for Swift :
var Products:NSMutableArray = NSMutableArray()
override func viewDidAppear(animated: Bool) {
if let Produtos = NSUserDefaults.standardUserDefaults().valueForKey("Produtos") {
Products = Produtos as! NSMutableArray
}
}
func InsertProducts(productCode:String){
//COPY Products Atual for auxMutable
var auxMutable = Products.mutableCopy()
//Add object in auxMutable
auxMutable.addObjectsFromArray([productCode])
//in line back data to Array Products and make cast to NSMutableArray
Products = auxMutable as! NSMutableArray
//Refresh Data of NSUserDefaults
NSUserDefaults.standardUserDefaults().setObject(Products, forKey: "Produtos")
}
#IBAction func Bt_New_Product(sender: AnyObject) {
var ProductName:String = TXT_NameProduct.text
InsertProducts(ProductName)
}
This work for me!!!

i found same issue and found solution hope it will help some one.
arrayOfferId = defaults.objectForKey("offerId")?.mutableCopy() as! NSMutableArray
NSUserDefaults returns immutable objects, even if you put in mutable ones. You must call -mutableCopy on the returned value to get a mutable collection. so when you get value from NSUserDefault use mutableCopy()

[NSUserDefaults dictionaryForKey] returns an immutable dictionary (NSDictionary) and you cannot force it to be mutable by casting it to NSMutableDictionary.
Instead you must create the mutable dictionary using mutableCopy, overwrite the element and then re-assigning the dictionary back into NSUserDefaults:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *storedIpDictionary = [[userDefaults dictionaryForKey:#"dictDeviceIp"] mutableCopy];
NSString *key = self.currentDeviceNameText.text;
NSString *ipAddressTemp = [storedIpDictionary objectForKey:key];
// Don't need this line
//[storedIpDictionary removeObjectForKey:key];
storedIpDictionary[key] = ipAddressTemp;
[userDefaults setObject:storedIpDictionary
forKey:#"dictDeviceIp"];

Related

How to Store multiple dictionary in NSUserDefaults

I have two Viewcontrollers in FirstVC i build 5 UITextField for registration ,this TextField value are stroed in dictionary finally the dictionary stored in NSUserdefault then in SecondVC i want to show this data
My problem is that each time when i add new discretionary in NSUserdefault The old one dictionary was replaced
i want data of all dictionary.
below is code of my FirstVC
-(void)btnReg
{
//data add in disctionary
for (int i=1; i<=5; i++)
{
UITextField *txtTemp=(UITextField *)[self.view viewWithTag:i];
[discRege setObject:[NSNumber numberWithInt:count] forKey:#"no"];
[discRege setObject:txtTemp.text forKey:[arraylblName objectAtIndex:i-1]];
}
//dictionary add in nsuserdefault
[[NSUserDefaults standardUserDefaults]setObject:discRege forKey:#"ABC"];
[[NSUserDefaults standardUserDefaults]synchronize];
//push to SecondVc
secondViewController *objSec=[[secondViewController alloc]init];
[self.navigationController pushViewController:objSec animated:YES];
[self.navigationController setNavigationBarHidden:false];
}
below is code of my SecondVC
ArratTemp =[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"] ;
if (!ArratTemp )
{
ArratTemp =[[NSMutableArray alloc]init];
}
else
{
ArratTemp = [[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"]mutableCopy];
}
NSLog(#"%#",ArratTemp);
Every time you are using the same key and replacing the existing dictionary object...
// Using the same key will overwrite the last saved dictionary.
[[NSUserDefaults standardUserDefaults] setObject:discRege forKey:#"ABC"];
Instead of storing it as a dictionary, store it as an array of dictionaries. Whenever you add new registration, fetch the saved array, add new dictionary object into it and update the userDefaults with that array.
mutableArray = [[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"] mutableCopy];
[mutableArray addObject:discReg];
[[NSUserDefaults standardUserDefaults] setObject:mutableArraay forKey:#"ABC"];
[[NSUserDefaults standardUserDefaults] synchronize];
Hope it helps.
You're overwriting the same disctionary every time.
You have two solutions :
Solution 1.
Store different dictionaries under different keys, not all under "ABC". So in your for loop can use the index (i) to have multiple entries, instead of just ABC every time. Here it's a simple matter you can resolve yourself. Make sure to not store everything under the same Key, and you'll find them ;) For example, you could save under [NSNumber numberWithInt:i]and then browse your NSUserDefaults for 0, 1, 2, 3... and so on. I recommend against this btw, solution 2 is the way to go.
Solution 2.
Store all your dictionaries in an array, and then store the array in the NSUserDefaults.
For that, simply create an NSMutableArray that you keep empty, then add dictionaries in it !
NSMutableArray dataArray = [[NSMutableArray alloc]init];
for (int i=1; i<=5; i++)
{
//Creating new dictionary
NSMutableDictionary *currentDict = [[NSMutableDictionary alloc]init];
//Getting the text we want
UITextField *txtTemp =(UITextField *)[self.view viewWithTag:i];
NSString *text = txtTemp.text;
//This is here because you had it
[currentDict setObject:[NSNumber numberWithInt:count] forKey:#"no"];
//All dictionaries will have key = name of the Label,
//but you could change it to something static, like
// "Content" for example. It'll be easier to find later
[currentDict setObject:text forKey:[arraylblName objectAtIndex:i-1]];
//Adding that newly formed dictionary to the mutable array.
[dataArray addObject:currentDict];
}
//Adding the array containing dictionaries to the NSUSerDefaults
[[NSUserDefaults standardUserDefaults]setObject:dataArray forKey:#"ABC"];
Note : I'm not exactly sure what you're doing with the dictionaries in the for loop, but since you didn't show the code, I'm guessing its' not part of the question. With my answer you have enough information to make some corrections if needed. All you need to remember is :
Create a dictionary per answer, and not one for all
Put each dictionary in the same array
Save the array (containing all the dictionaries)

Add object to array within NSMutableDictionary loaded from NSUserDefaults

I have an array inside a NSMutableDictionary and i want to add objects to it. With my current approach I get an error saying that the array is immutable.
I think the problem lies when I´m saving the dictionary to NSUserDefaults. I´m retrieving the is it a NSDictionary but I am at the same time creating a new NSMutableDictionary with the contents.
However, the array seems to be immutable. How do I replace an array inside of a dictionary?
My dictionary looks like this:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
I am trying to add objects like this:
[[[mutDict objectForKey:#"firstKey"] objectForKey:#"theArray"] addObject:[NSNumber numberWithInt:5]];
I am able to add objects to the array inside mutDict before its saved to NSUserDefaults
The error message I get when I try to add to the array inside the dictionary after loading it from NSUserDefaults:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
Here's what the documentation for dictionaryForKey: says on NSUserDefaults:
Special Considerations
The returned dictionary and its contents are immutable, even if the values you >originally set were mutable.
So when you retrieve your dictionary from NSUserDefaults the dictionary itself and all of the collections inside it are immutable. You can make the top level dictionary mutable (which I assume you are doing), but that won't propagate down into the now immutable NSArrays which are values in the dictionary.
The only way to get around this is to go through the dictionary that's returned and replace the immutable NSArrays with their mutable counterparts. It might look something like this.
- (NSMutableDictionary *)deepMutableCopyOfDictionary:(NSDictionary *)dictionary
{
NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
for (id key in [mutableDictionary allKeys]) {
id value = mutableDictionary[key];
if ([value isKindOfClass:[NSDictionary class]]) {
// If the value is a dictionary make it mutable and call recursively
mutableDictionary[key] = [self deepMutableCopyOfDictionary:dictionary[key]];
}
else if ([value isKindOfClass:[NSArray class]]) {
// If the value is an array, make it mutable
mutableDictionary[key] = [(NSArray *)value mutableCopy];
}
}
return mutableDictionary;
}
To be honest though it sounds like you're using NSUserDefaults for something a lot more complex then it is intended for. If you want to persist complex data structures then you should look into something like Core Data, or if that looks to be a bit overkill take a look at NSKeyedArchiver.
You can add object directly to the array:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
//Since Objective-C objects are always passed by reference (using pointers) you can add object to the array
[array addObject:[NSNumber numberWithInt:55]];
Swift example of adding object to array which is part of a dictionary.
let arr = [0] // note that initial array may be immutable
var dict = ["fK": ["a":arr]] // even if "arr" will be mutable, but "dict" immutable
dict["fK"]!["a"]!.append(3) // this will not work. "dict" must be mutable
println(dict) //[fK: [a: [0, 3]]]
Another approach
var arr = [0] // initial array must be mutable
var dict = ["fK": ["a":arr]] // in both cases dictionary must be mutable
arr.append(3)
let newArr = arr
dict["fK"]!["a"]! = newArr // because we change it's content
println(dict) //[fK: [a: [0, 3]]]

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

Saving NSdictionary in NSUserDefaults

I tried to save an NSDictionary in NSUSerDefaults, but I get the following error:
Attempt to insert non-property value
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if(defaults) {
[defaults setBool: YES forKey: #"disableGetStarted"];
[defaults setObject: [json mutableCopy] forKey: #"user"];
[defaults synchronize];
NSLog(#"defaults %#", [defaults objectForKey: #"user"]);
}
Where json is an NSDictionary.
What can I do?
json may be a dictionary but all of the contents of the dictionary must be legal values to be stored in user defaults. All must be instances of: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary.
If you only store standard objects inside the dictionary like NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary or a combination of them you don't have to do anything special.
However, if you have instances of custom objects in it (i.e. classes that you've created) you first need to convert it into a compatible type (e.g. NSData). You can do this using the code below:
NSData* data=[NSKeyedArchiver archivedDataWithRootObject:json];
[NSUserDefaults standardUserDefaults] setObject:data forKey:#"user"]
For this method to work, you ned to implement these 2 methods in the custom classes you are trying to save:
- (id)initWithCoder:(NSCoder*)coder
- (void)encodeWithCoder:(NSCoder*)coder;
To get the dictionary back from NSUserDefaults (decode) you can use:
NSData* data = [[NSUserDefaults standardUserDefaults] objectForKey:#"user"];
NSDictionary* json = [NSKeyedUnarchiver unarchiveObjectWithData:data];
EDIT
To check if your json object contains any [NSNull null] values, add this piece of code before you are making your insert into NSUserDefaults
for (id val in [json allValues])
{
if ([val isKindOfClass:[NSNull class]])
{
NSLog(#"This bad! NSNull should not be in the dictionary");
break;
}
}
If you get any This is bad... messages in the console, then you have 2 choices.
1. Use the archiving/unarchiving method I described above
2. Replace the NSNull objects from the dictionary with other values (e.g. empty strings) if this does not break your code.
One or more of the objects in your dictionary are not NSString, NSArray, NSDictionary, NSDate or NSData, the only objects that may be present. This code may help, it gives you more details why your dictionary is not valid:
- (BOOL)isValidPropertyList:(id)object {
//
// Check if `object` is a valid property list.
//
// # Return Value
// `YES` if the receiver is a valid property list, `NO` otherwise.
//
// # Discussion
// Intended to be called on NSArray and NSDictionary object to verify if the
// receiver is a valid property list. Will emit a description of any
// detected error.
//
NSData *xmlData;
NSString *error;
xmlData=[NSPropertyListSerialization
dataFromPropertyList:object
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error
];
if(xmlData)
{
return YES;
}
else
{
NSLog(#"Tested object is not a valid property list: %#",error);
}
return NO;
}
NSDictionary *dict = [[NSDictionary alloc] init];
dict = json;
[NSUserDefaults standardDefaults] setObject:dict forKey:#"user"];
NSUserDefaults doesn't distinguish between mutable and immutable objects, so when you get it back it'll be immutable. So if you make a mutable dictionary by chance ->
[[NSUserDefaults standardDefaults] setObject:dict forKey:#"user"] mutableCopy];
All objects of the dictionary json must be instances of: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary.

How do I store and retrieve an NSMutableDictionary to and from NSUserDefaults?

I tried to do this to store an empty dictionary in NSUserDefaults.
NSMutableDictionary* fruits = [NSMutableDictionary alloc];
[defaults setObject:fruits forKey:#"fruits"];
and then later this to retrieve it.
NSMutableDictionary* fruits = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:#"fruits"]];
However, retrieving the dictionary crashes my application. Why? How do I store a dictionary in NSUserDefaults?
You get a immutable dictionary back. You do not need to "capsulate" it in another dictionary. If you want to make it mutable write:
NSMutableDictionary* animals = [[defaults objectForKey:#"animals"] mutableCopy];
The NSUserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Booleans, and URLs. A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.
Values returned from NSUserDefaults are immutable, even if you set a
mutable object as the value. For example, if you set a mutable string
as the value for "MyStringDefault", the string you later retrieve
using stringForKey: will be immutable.
Note: The user defaults system, which you programmatically access through the NSUserDefaults class, uses property lists to store objects representing user preferences. This limitation would seem to exclude many kinds of objects, such as NSColor and NSFont objects, from the user default system. But if objects conform to the NSCoding protocol they can be archived to NSData objects, which are property list–compatible objects. For information on how to do this, see ““Storing NSColor in User Defaults”“; although this article focuses on NSColor objects, the procedure can be applied to any object that can be archived.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsuserdefaults_Class/Reference/Reference.html
You can use:
Save:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:mutableArray];
[[NSUserDefaults standardUserDefaults] setObject:stack forKey:#"Your Key"];
Retrieve:
NSMutableArray *mutableArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
You need to init your dictionary and set is as object later. This way works, it's the same as your example but just with properly initialization.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"someValue", #"someKey", nil];
[defaults setObject:dict forKey:#"slovnik"];
[dict release];
NSLog(#"READ: %#", [defaults objectForKey:#"slovnik"]);
NSMutableDictionary *newDict = [[NSMutableDictionary alloc] initWithDictionary:[defaults objectForKey:#"slovnik"]];
NSLog(#"READ2: %#", newDict);
Now I get to log console and app do not crash:
2012-04-12 08:47:55.030 Test[12179:f803] READ: {
someKey = someValue;
}
2012-04-12 08:47:55.031 Test[12179:f803] READ2: {
someKey = someValue;
}
NSMutableDictionary* fruits = [NSMutableDictionary alloc];
should be
NSMutableDictionary* fruits = [[NSMutableDictionary alloc] init];
You need to always initialize objects after allocating them.

Resources