Saving NSdictionary in NSUserDefaults - ios

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.

Related

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

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

Getting values from userdefaults and creating an NSMutableDictionary

I am trying to create a dictionary which is a replica for NSUserdefaults. I want the dictionary to contain same values and keys.
But, we need to convert bool and int values to NSNumber when we save it to dictionary. Right now i am doing the following. But not sure which value is bool value and int value. If I can get to know the type of the value I can do rest. Is there any way to check the value whether it is bool or int.
NSArray *availableUserDefaultsKeys = [NSArray arrayWithObjects:#"Key1", #"Key2",nil];
NSMutableDictionary *userDefaultsDictionary = [NSMutableDictionary dictionary];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
for (NSString *key in availableUserDefaultsKeys) {
id value = [userDefaults objectForKey:key];
if (value != nil) { // Is there any way to check whether the value is bool or int here
[userDefaultsDictionary setObject:value forKey:key];
} else {
[userDefaultsDictionary setObject:[NSNull null] forKey:key];
}
}
I have checked the debug.plist which has all the user defaults stored, In that we have type field where it specifies the type. Can we get the type from this field.
You can use this way:
// v is NSNumber
if ((strcmp([v objCType], #encode(BOOL) == 0) {
// this is BOOL
}
And there are #encode(int) #encode(float)
You should avoid directly saving seperated data to NSUserDefaults, you can pack them in an object, and this object should conform NSCoding protocol. Then after you read the data out, you will know the exact type.

Save id type object in NSUserDefaults

I want to save id type object value in NSUserDefaults.
-(IBAction)sendOrderReady:(id)sender
{
NSUserDefaults *d = [NSUserDefaults standardUserDefaults];
[d setValue:sender forKey:#"sender"];
// [d setObject:sender forKey:#"sender"];
[d synchronize];
}
from the NSUserDefaults class description
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.
so if your id object isn't one of the above instances, then you will have to convert it to one of them, and this question is what exactly you're looking for.
With NSUserDefaults you can save objects from the following class types:
NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary
If you want to store any other type of object then you need to archive it or wrap it in an instance of NSData
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// saving an NSString
[prefs setObject:#"iPhone" forKey:#"keyToLookupString"];
// saving an NSInteger
[prefs setInteger:442 forKey:#"integerKey"];
// saving a Double
[prefs setDouble:5.15 forKey:#"doubleKey"];
// saving a Float
[prefs setFloat:123.45678 forKey:#"floatKey"];
// This is suggested to synch prefs, but it is not needed
[prefs synchronize];

Save NSArray to NSUserDefaults issues (using custom object in array)

I try to save my object to NSUserDefaults. But when I call this method again it is not have any info about previous operation.
There is my method below:
- (void)addToCart {
if([[NSUserDefaults standardUserDefaults] objectForKey:kCart]) {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSMutableArray *products = [[NSMutableArray alloc] initWithArray:[prefs objectForKey:kCart]];
[products addObject:self.product];
[prefs setObject:products forKey:kCart];
[prefs synchronize];
[products release];
}
else {
//Saving...
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:[NSArray arrayWithObjects:self.product, nil] forKey:kCart];
[prefs synchronize];
}
}
I need to save a collection with a products to NSUserDefault. I wrap my object to NSArray and save it but it doesn't work.
Everything put into NSUserDefaults must be a valid property list object (NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary). All collection elements must themselves also be property list objects.
In order to save non-PL objects into NSUserDefaults, you must first convert the object into a PL object. The most generic way to do this is by serializing it to NSData.
Serializing to NSData is handled with NSKeyedArchiver. See Storing NSColor in User Defaults for the canonical example of this. (That document is very old and still references NSArchiver which will work fine for this problem, but NSKeyedArchiver is now the preferred serializer.)
In order to archive using NSKeyedArchiver, your object must conform to NSCoding as noted by #harakiri.
You need to conform to the <NSCoding> protocol and implement -initWithCoder: and -encodeWithCoder: in your custom object.
See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/Reference/Reference.html

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