13

I couldn't store an NSArray in NsUserDefaults. The app gets crashed
in this line [savedData setObject:jsonValue forKey:@"JSONDATA"];

Below is my code. and i have mention my log error below

NSArray *jsonValue =[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSUserDefaults *savedData = [NSUserDefaults standardUserDefaults];
[savedData setObject:jsonValue forKey:@"JSONDATA"];
[savedData synchronize];

Error log:

*** Terminating app due to uncaught exception '`NSInvalidArgumentException`', reason: '*** -

[NSUserDefaults setObject:forKey:]: attempt to insert non-property list object <CFBasicHash 0x8c62b10 [0x1d2aec8]>{type = immutable dict, count = 3,
    entries =>
Forge
  • 6,538
  • 6
  • 44
  • 64
iworld
  • 345
  • 1
  • 4
  • 17
  • Can you check the content of the jsonValue ? – Midhun MP Sep 02 '14 at 06:49
  • I think this is what you're looking for: http://stackoverflow.com/questions/19720611/attempt-to-set-a-non-property-list-object-as-an-nsuserdefaults#19720674 – grimdox Sep 02 '14 at 06:54
  • Please NSLog the value that you are trying to store. All we have is an error message that was cut off long before things get interesting. – gnasher729 Sep 02 '14 at 07:18

6 Answers6

43

Yes they can not be saved like this in NSUserDefaults.

I am writing a code below please have a look and for more study go look apple docs okay.

Code is here:

//For Saving

NSData *dataSave = [NSKeyedArchiver archivedDataWithRootObject:yourArrayToBeSave]];
[[NSUserDefaults standardUserDefaults] setObject:dataSave forKey:@"array"];

[[NSUserDefaults standardUserDefaults] synchronize]; // this will save you UserDefaults

//For retrieving

NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"array"];
NSArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
kshitij godara
  • 1,523
  • 18
  • 30
3

While kshitij's answer is correct, sadly, it doesn't end there. If you want to save custom objects inside your NSUserDefaults, your custom object has to implement the NSCoding protocol and override the initWithCoder and encodeWithCoder methods. An example of such can be like:

@interface Book : NSObject <NSCoding>
@property NSString *title;
@property NSString *author;
@property NSUInteger pageCount;
@property NSSet *categories;
@property (getter = isAvailable) BOOL available;
@end

@implementation Book

#pragma mark - NSCoding

    - (id)initWithCoder:(NSCoder *)decoder {
        self = [super init];
        if (!self) {
            return nil;
        }

        self.title = [decoder decodeObjectForKey:@"title"];
        self.author = [decoder decodeObjectForKey:@"author"];
        self.pageCount = [decoder decodeIntegerForKey:@"pageCount"];
        self.categories = [decoder decodeObjectForKey:@"categories"];
        self.available = [decoder decodeBoolForKey:@"available"];

        return self;
    }

    - (void)encodeWithCoder:(NSCoder *)encoder {
        [encoder encodeObject:self.title forKey:@"title"];
        [encoder encodeObject:self.author forKey:@"author"];
        [encoder encodeInteger:self.pageCount forKey:@"pageCount"];
        [encoder encodeObject:self.categories forKey:@"categories"];
        [encoder encodeBool:[self isAvailable] forKey:@"available"];
    }

@end
avismara
  • 5,141
  • 2
  • 32
  • 56
1

I think jsonValue is NSDictionary not NSArray..

 id jsonValue = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
    if ([jsonValue isKindOfClass:[NSDictionary class]])
    {
   NSData *data = [NSKeyedArchiver archivedDataWithRootObject:jsonvalue];
   [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"JSONDATA"];
    [savedData synchronize];
    }

//(OR)

NSDictionary *jsonValue =[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:jsonvalue];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"JSONDATA"];
[savedData synchronize];

Also I think you cannot store the NSDictionary directly into NSUserDefaults..Go through @kshitij godara to get it done/save..

You can use NSKeyedArchiver to write out your dictionary to an NSData.

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:jsonvalue];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"JSONDATA"];

For retrieving data:

NSData *dictionaryData = [defaults objectForKey:@"JSONDATA"];
NSDictionary *dictionary = [NSKeyedUnarchiver unarchiveObjectWithData:dictionaryData];

Hope this helps you..!

Vidhyanand
  • 5,369
  • 4
  • 26
  • 59
  • 1
    You start with NSData. You convert the NSData to a dictionary or an array. You convert the dictionary or array to NSData and store it. Can you see the two completely pointless steps in your code? – gnasher729 Sep 02 '14 at 07:23
1

try like this

 NSMutableArray *jsonValue =[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    NSData *yourEncodedObject = [NSKeyedArchiver archivedDataWithRootObject: jsonValue];

       NSUserDefaults *savedData = [NSUserDefaults standardUserDefaults];
        [userData setObject: yourEncodedObject forKey:@"JSONDATA"];
    [savedData synchronize];
Sport
  • 8,570
  • 6
  • 46
  • 65
1

NSUserDefaults accepts property list objects - NSDictionary, NSArray, NSString, NSNumber, NSDate and NSData. JSON documents contain NSDictionary, NSArray, NSString, NSNumber and NSNull. You see the difference - property list objects cannot contain NSNull values.

If your JSON object contains an NSNull object (the JSON document that you parsed contained a "null" value), then you can't store it into NSUserDefaults. Apart from that, all the comments saying that you can't store JSON data in NSUserDefaults are nonsense. It's only NSNull objects that cause a problem.

There is a very, very simple solution to your problem. Just store the original json data as an NSData object in NSUserDefaults. Then when you read the NSUserDefaults, you get the NSData and parse them. Instead of

NSArray *jsonValue =[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSUserDefaults *savedData = [NSUserDefaults standardUserDefaults];
[savedData setObject:jsonValue forKey:@"JSONDATA"];
[savedData synchronize];

you just write

NSUserDefaults *savedData = [NSUserDefaults standardUserDefaults];
[savedData setObject:data forKey:@"JSONDATA"];
[savedData synchronize];
gnasher729
  • 51,477
  • 5
  • 75
  • 98
0

NSUserDefaults should only be used for up to 500k of data (https://forums.developer.apple.com/message/50696#50696)

If storing larger amounts apps may crash, from personal experience I've seen apps storing 1MB of data crashing at/shortly after [NSUserDefaults setObject:] and [NSUserDefaults synchronise];

To check how much data a live app is using, open Xcode -> Window -> Devices, select your Device, select your app in Installed Apps and download the App Container. Defaults are stored in AppData->Preferences->your.app.plist

An alternative to saving data to the user defaults is to write to a file:

-(void)saveObject:(NSObject *)object toFile:(NSString *)name {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *appFile = [documentsDirectory stringByAppendingPathComponent:name];
    [NSKeyedArchiver archiveRootObject:object toFile:appFile];
}

-(NSObject *)loadFromFile:(NSString *)name {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *appFile = [documentsDirectory stringByAppendingPathComponent:name];

    if([[NSFileManager defaultManager] fileExistsAtPath:appFile])
        return [NSKeyedUnarchiver unarchiveObjectWithFile:appFile];
    else{
        NSLog(@"No File: %@", name) ;
        return nil ;
    }

}
A.Badger
  • 4,279
  • 1
  • 26
  • 18