iworld
iworld

Reputation: 345

App crashes when storing NSArray in NSUserDefaults

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 =>

Upvotes: 13

Views: 15834

Answers (6)

kshitij godara
kshitij godara

Reputation: 1523

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

Upvotes: 43

A.Badger
A.Badger

Reputation: 4429

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

}

Upvotes: 0

gnasher729
gnasher729

Reputation: 52538

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

Upvotes: 1

Vidhyanand
Vidhyanand

Reputation: 5369

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..!

Upvotes: 1

Sport
Sport

Reputation: 8945

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

Upvotes: 1

avismara
avismara

Reputation: 5149

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

Upvotes: 3

Related Questions