Why NSUserDefaults failed to save NSMutableDictionary in iOS?
Solution 1
I found out one alternative, before save, I encode the root object (NSArray
object) using NSKeyedArchiver
, which ends with NSData
. Then use UserDefaults save the NSData
.
When I need the data, I read out the NSData
, and use NSKeyedUnarchiver
to convert NSData
back to the object.
It is a little cumbersome, because i need to convert to/from NSData
everytime, but it just works.
Here is one example per request:
Save:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *arr = ... ; // set value
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
[defaults setObject:data forKey:@"theKey"];
[defaults synchronize];
Load:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:@"theKey"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
The element in the array implements
@interface CommentItem : NSObject<NSCoding> {
NSString *value;
}
Then in the implementation of CommentItem
, provides two methods:
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:value forKey:@"Value"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
self.value = [decoder decodeObjectForKey:@"Value"];
return self;
}
Anyone has better solution?
Thanks everyone.
Solution 2
If you're saving an object in user defaults, all objects, recursively, all the way down, must be property list objects. Conforming to NSCoding doesn't mean anything here-- NSUserDefaults
won't automatically encode them into NSData
, you have to do that yourself. If your "list of object which implements NSCoding
" means objects that are not property list objects, then you'll have to do something with them before saving to user defaults.
FYI the property list classes are NSDictionary
, NSArray
, NSString
, NSDate
, NSData
, and NSNumber
. You can write mutable subclasses (like NSMutableDictionary
) to user preferences but the objects you read out will always be immutable.
Solution 3
Are all of your keys in the dictionary NSString
s? I think they have to be in order to save the dictionary to a property list.
Solution 4
Simplest Answer :
NSDictionary
is only a plist object , if the keys are NSStrings.
So, Store the "Key" as NSString
with stringWithFormat
.
Solution :
NSString *key = [NSString stringWithFormat:@"%@",[dictionary valueForKey:@"Key"]];
Benefits :
- It will add String-Value.
- It will add Empty-Value when your Value of Variable is
NULL
.
Solution 5
Have you considered looking at implementing the NSCoding
Protocol? This will allow you encode and decode on the iPhone with two simple methods that are implemented with the NSCoding
. First you would need to adding the NSCoding
to your Class.
Here is an example:
This is in the .h file
@interface GameContent : NSObject <NSCoding>
Then you will need to implement two methods of the NSCoding Protocol.
- (id) initWithCoder: (NSCoder *)coder
{
if (self = [super init])
{
[self setFoundHotSpots:[coder decodeObjectForKey:@"foundHotSpots"]];
}
return self;
}
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject:foundHotSpots forKey:@"foundHotSpots"];
}
Check out the documentation on NSCoder for more information. That has come in really handy for my projects where I need to save the state of the application on the iPhone if the application is closed and restore it back to it's state when its back on.
The key is to add the protocol to the interface and then implement the two methods that are part of NSCoding
.
I hope this helps!
BlueDolphin
Coding is my favorate game, it's fun and addicting.
Updated on January 05, 2020Comments
-
BlueDolphin over 4 years
I'd like to save an
NSMutableDictionary
object inNSUserDefaults
. The key type inNSMutableDictionary
isNSString
, the value type isNSArray
, which contains a list of object which implementsNSCoding
. Per document,NSString
andNSArray
both are conform toNSCoding
.I am getting this error:
[NSUserDefaults setObject: forKey:]: Attempt to insert non-property value.... of class NSCFDictionary.
-
Srivathsalachary Vangeepuram over 15 yearsI should also mention that an NSDictionary is only a property list object if the keys are NSStrings. In general you can use other objects as dictionary keys, but where property lists are required the keys must be strings.
-
BlueDolphin over 15 yearsthe keys are NSString. but the value is NSArray, whose element is a customized class which implements NSCoding. It appears that solution not working because UserDefaults only supports 6 types of class, not consider NSCoding.
-
Anthony Main about 15 yearsDo you have a code sample you can share, I've been trying to do this in post (537044) but with no joy
-
John Wright almost 15 yearsThis worked nicely for me. I was trying to encode an NSArray of NSDictionary objects where some of the keys were not strings. Simply using the NSKeyedArchiver and Unarchiver on the NSArray got rid of the "Attempt to insert non-property value" error.
-
NicTesla over 12 yearsI came across the same problem, when i wanted to store NSURL as an object in a NSDictionary and store it in the NSUserDefaults. I had to change it into an NSString, than it worked.
-
Marc about 12 yearsWhen do I need to release the loaded object? No alloc was called, so I would assume it is autorelased?
-
Joey almost 12 yearsGreat! In my case, I tried to use NSNumber as a key of NSDictionary in user defaults. I have to change it into an NSString.
-
thanhbinh84 over 11 yearswe might need add [defaults synchronize] for saving
-
ArtOfWarfare over 10 yearsSuch retarded behavior is what leads to inexperienced coders writing their entire data model as dictionaries instead of making proper data objects? How freaking hard can it be for Apple to write foundation classes that utilize the fact that obj-c has introspection and take care of boxing and unboxing for us?
-
Mark Pauley over 10 yearsI had a similar problem: my keys were NSNumbers, which are apparently not allowed in keys of plist dictionaries.
-
Tom Andersen almost 9 yearsThe solution posted here may work for a while, but when you decide to remove or change the class, you will find yourself locked in a mess. A better solution is make your object write its state using NSNumbers, Strings, etc into a dictionary, then store that. Future proof.