NSCoding: Custom Class unarchived, but with empty/nil class members - ios

I want to persist an NSDictionary, filled with custom Object to disc:
NSDictionary *menuList = [[NSMutableDictionary alloc]initWithDictionary:xmlParser.items];
//here the "Menu List"`s Object are filled correctly
//persisting them to disc:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex:0];
NSString *fileName = [NSString stringWithUTF8String:MENU_LIST_NAME];
NSString *filePath = [directory stringByAppendingPathComponent:fileName];
//saving using NSKeyedArchiver
NSData* archiveData = [NSKeyedArchiver archivedDataWithRootObject:menuList];
[archiveData writeToFile:filePath options:NSDataWritingAtomic error:nil];
//here the NSDictionary has the correct amount of Objects, but the Objects` class members are partially empty or nil
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSDictionary *theMenu = (NSDictionary*)[NSKeyedUnarchiver unarchiveObjectWithData:data];
Here the .m of the Custom Object (the kind of object stored in the NSDictionary)
- (id)initWithTitle:(NSString*)tTitle level:(NSString*)tLevel state:(NSString*)tState visible:(BOOL)tVisible link:(NSString*)tLink linkType:(NSString*)tLinkType anId:(NSString*)tId {
if ((self = [super init])) {
self.anId = tId;
self.title = tTitle;
self.level = tLevel;
self.state = tState;
self.visible = tVisible;
self.link = tLink;
self.linkType = tLinkType;
}
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.anId forKey:#"anId"];
[encoder encodeObject:self.level forKey:#"level"];
[encoder encodeObject:self.state forKey:#"state"];
[encoder encodeBool:self.visible forKey:#"visible"];
[encoder encodeObject:self.title forKey:#"title"];
[encoder encodeObject:self.link forKey:#"link"];
[encoder encodeObject:self.linkType forKey:#"linkType"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if(self == [super init]){
self.anId = [decoder decodeObjectForKey:#"anId"];
self.level = [decoder decodeObjectForKey:#"level"];
self.state = [decoder decodeObjectForKey:#"state"];
self.visible = [decoder decodeBoolForKey:#"visible"];
self.title = [decoder decodeObjectForKey:#"title"];
self.link = [decoder decodeObjectForKey:#"link"];
self.linkType = [decoder decodeObjectForKey:#"linkType"];
}
return self;
}
#end
I dont know why the objects are unarchived correctly, but the object´s members are lost somewhere. I assume there must be an error somewhere in the NSCoding methods, but I just can´t find it, any help much appreciated.

When you implement the initWithCoder: method, you need to call super properly:
if (self = [super initWithCoder:decoder]) {
The instance that is being unarchived has more attributes than just the additions in your specific class. You also don't want to be checking equality with self, you want to be assigning to self.

Related

Unarchive custom object with secure coding in iOS

I'm encoding an array of objects that conform to NSSecureCoding successfully like this.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array requiringSecureCoding:YES error:nil];
How do I unarchive this data? I'm trying like this but it's returning nil.
NSError *error = nil;
NSArray<MyClass *> *array = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSArray class] fromData:data error:&error];
This is the error it returns.
The data couldn’t be read because it isn’t in the correct format.
Here is a simple example
#interface Secure:NSObject<NSSecureCoding> {
NSString* name;
}
#property NSString* name;
#end
#implementation Secure
#synthesize name;
// Confirms to NSSecureCoding
+ (BOOL)supportsSecureCoding {
return true;
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:self.name forKey: #"name"];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
self = [self init];
if (self){
self.name = [coder decodeObjectOfClass:[NSString class] forKey:#"name"];
}
return self;
}
#end
Usage
Secure *archive = [[Secure alloc] init];
archive.name = #"ARC";
NSArray* array = #[archive];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array requiringSecureCoding:YES error:nil];
NSArray<Secure *> *unarchive = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSObject class] fromData:data error:nil];
NSLog(#"%#", unarchive.firstObject.name);

Add Data to a Singleton instance array

I have a class "DataStorage", in which i wish to use NSCoding protocols to save data. However, when try to add to an array of that class,
[DataStorage sharedData].firstNameArray addObject:firstName:];
When viewing the breakpoint in the DataStorage class it shows the array to be of "nil" value
Here's the class implementation...
#import "DataStorage.h"
#implementation DataStorage
#synthesize firstNameArray = _firstNameArray;
#synthesize surnameArray = _surnameArray;
#synthesize companyArray = _companyArray;
#synthesize positionArray = _positionArray;
#synthesize emailArray = _emailArray;
#synthesize mobileArray = _mobileArray;
#synthesize productArray = _productArray;
+ (instancetype)sharedData {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
});
return sharedInstance;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_firstNameArray = [decoder decodeObjectForKey:#"DataStorageFirstNameKey"];
_surnameArray = [decoder decodeObjectForKey:#"DataStorageSurnameKey"];
_companyArray = [decoder decodeObjectForKey:#"DataStorageCompanyKey"];
_positionArray = [decoder decodeObjectForKey:#"DataStoragePositionKey"];
_emailArray = [decoder decodeObjectForKey:#"DataStorageEmailKey"];
_mobileArray = [decoder decodeObjectForKey:#"DataStorageMobileKey"];
_productArray = [decoder decodeObjectForKey:#"DataStorageProductKey"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_firstNameArray forKey:#"DataStorageFirstNameKey"];
[encoder encodeObject:_surnameArray forKey:#"DataStorageSurnameKey"];
[encoder encodeObject:_companyArray forKey:#"DataStorageCompanyKey"];
[encoder encodeObject:_positionArray forKey:#"DataStoragePositionKey"];
[encoder encodeObject:_emailArray forKey:#"DataStorageEmailKey"];
[encoder encodeObject:_mobileArray forKey:#"DataStorageMobileKey"];
[encoder encodeObject:_productArray forKey:#"DataStorageProductKey"];
}
+ (NSString*)filePath
{
static NSString* filePath = nil;
if (!filePath) {
filePath =
[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
stringByAppendingPathComponent:#"datastorage"];
}
return filePath;
}
+ (instancetype)loadInstance
{
NSData* decodedData = [NSData dataWithContentsOfFile: [DataStorage filePath]];
if (decodedData) {
DataStorage * dataStorage = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return dataStorage;
}
return [[DataStorage alloc] init];
}
- (void)save
{
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self];
[encodedData writeToFile:[DataStorage filePath] atomically:YES];
}
#end
First of all, the _firstNameArray should be a mutable array but the array you get from [decoder decodeObjectForKey:#"DataStorageFirstNameKey"]; is not mutable, so you must create one.
Second, when you first use the instance, there is not file, so you will get nil in _fileNameArray, at this time, you should create a empty mutable array instead so you can add item in it.
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_firstNameArray = [[decoder decodeObjectForKey:#"DataStorageFirstNameKey"] mutableCopy];
if (!_firstNameArray) {
_firstNameArray = [NSMutableArray array] ;
}
}
return self;
}
So are the other properties.

Unable to save table data via NSCoding

I have an array of Objects (containing an item name and a creation date) used to display table data. I want to save this array using NSCoding protocol. I call my saveDataToDisk method every time a "submit" button is pressed to submit a new table entry. However, my app crashes upon hitting submit. This is my first time learning how to save app data and I looked a several tutorials and my form seems to be the same. I also looked for several days on the internet and couldn't find a solution. What is wrong?
Here are my methods:
Decoder Method:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.itemArray = [decoder decodeObjectForKey:#"itemArray"];
return self;
}
Encoder Method:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.itemArray forKey:#"itemArray"];
}
Path Finder Method:
- (NSString*) pathForDataFile
{
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"appData"];
if([fileManager fileExistsAtPath:filePath] == NO)
{
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:NO attributes:nil error:nil];
}
NSString *fileName = #"Budgetlist.appData";
return [filePath stringByAppendingPathComponent: fileName];
}
Save Data Method:
- (void) saveDataToDisk
{
NSString * path = [self pathForDataFile];
NSMutableDictionary * rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue: [self itemArray] forKey:#"itemArray"];
[NSKeyedArchiver archiveRootObject: rootObject toFile: path];
}
Load Data Method:
- (void) loadDataFromDisk
{
NSString * path = [self pathForDataFile];
NSDictionary * rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
[self setItemArray: [rootObject valueForKey:#"itemArray"]];
}
My Button:
- (IBAction)submitButton:(id)sender {
if (self.textField.text.length > 0 && [self.textField.text floatValue]>0) {
self.item = [[Data alloc] init];
self.item.itemName = self.textField.text;
[self.itemArray addObject:self.item];
[self.tableView reloadData];
[self saveDataToDisk];
}
}
You are trying to save an NSArray with custom objects.
Your objects must implement NSCoding protocol as well.
Implement those:
- (id)initWithCoder:(NSCoder *)decoder
- (void)encodeWithCoder:(NSCoder *)encoder
in your "Data" class.
NSArray is going through all objects and call those methods for each object inisde an array

Unable to decode object from NSUserDefaults

I'm trying to decode my object from NSUserdefaults.
If I debug the function initWithCoder, it's executed okay, but when I look at my object that was decoded, all the properties are nil.
My initWithCoder function:
-(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if ( self != nil )
{
//decode the properties
self.SGTIN = [decoder decodeObjectForKey:#"SGTIN"];
self.GTIN = [decoder decodeObjectForKey:#"GTIN"];
self.SerialNumber = [decoder decodeObjectForKey:#"SerialNumber"];
self.Product = [decoder decodeObjectForKey:#"Product"];
self.DateTimeScanned = [decoder decodeObjectForKey:#"DateTimeScanned"];
self.Loaded = [decoder decodeBoolForKey:#"Loaded"];
}
return self;
}
My code to decode the object:
NSDictionary *tempDictionary = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
NSArray *tempArray =[tempDictionary allKeys];
for (NSString *key in tempArray){
if ([key length] > 8) {
if ([[key substringToIndex:8] isEqualToString:#"INXITEM:"]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedObject = [defaults objectForKey:key];
ScannedItem *SITemp = (ScannedItem *)[NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
}
}
}
Just found it...
It was because the property Loaded (bool) is a new property and the object that was saved in NSUserDefaults didn't contain that property.
Cleared my NSUserDefaults and everything is okay now...
You aren't calling the correct superclass method.
It should be
self = [super initWithCoder:decoder];
Try using - (void)encodeWithCoder:(NSCoder *)coder method in ScannedItem object class
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.SGTIN forKey:#"SGTIN"];
//encode all properties
}

NSCoding -decoder returns wrong values -iOS

I have written the following method to encode/decode data..
- (void) encode: (BOOL) encodeBool int: (NSNumber *) integer boolean:(BOOL) boolean key: (NSString *) keyStr {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *gameStatePath = [documentsDirectory stringByAppendingPathComponent:#"gameData"];
if (encodeBool == YES) {
NSMutableData *gameData = [NSMutableData data];
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:gameData];
if (integer) {
[encoder encodeInt:[integer intValue] forKey:keyStr];
}
else if (boolean) {
[encoder encodeBool:boolean forKey:keyStr];
}
[encoder finishEncoding];
[gameData writeToFile:gameStatePath atomically:YES];
[encoder release];
} else {
NSMutableData *gameData = [NSData dataWithContentsOfFile:gameStatePath];
if (gameData) {
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:gameData];
if (integer) {
NSLog(#"%d", [decoder decodeIntForKey:keyStr]);
}
else if (boolean) {
if ([decoder decodeBoolForKey:keyStr]==YES) {
NSLog(#"YES");
} else {
NSLog(#"NO");
}
}
[decoder finishDecoding];
[decoder release];
}
}
}
And some testing
[[GameData sharedData] encode:YES int: [NSNumber numberWithInt:100] boolean:NO key:#"testInt"];
[[GameData sharedData] encode:YES int:nil boolean:YES key:#"bool"];
[[GameData sharedData] encode:YES int:[NSNumber numberWithInt:1030] boolean:nil key:#"test"];
[[GameData sharedData] encode:NO int: [NSNumber numberWithInt:1] boolean:nil key:#"testInt"];
[[GameData sharedData] encode:NO int:nil boolean:YES key:#"bool"];
[[GameData sharedData] encode:NO int:[NSNumber numberWithInt:100] boolean:nil key:#"test"];
and output is
0
NO
1030
only the last one is correct.. Can someone tell me what I am doing wrong? Thanks
Your problem is that every time you call your method, you overwrite the file - erasing the values you encoded in previous calls. You should probably rewrite your method so that you encode all the values in a single call.
One alternative is to create a GameState object and have it implement NSCoding, then read and serialize it with +[NSKeyedArchiver archiveRootObject:toFile:] and deserialize it with +[NSKeyedUnarchiver unarchiveObjectWithFile:]. The code to do so looks a bit like this:
#interface GameState : NSObject <NSCoding>
#property (nonatomic) int someInt;
#property (nonatomic) BOOL someBool;
#property (nonatomic, strong) NSString *someString;
#end
static NSString *const BoolKey = #"BoolKey";
static NSString *const StringKey = #"StringKey";
static NSString *const IntKey = #"IntKey";
#implementation GameState
- (id)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
_someBool = [coder decodeBoolForKey:BoolKey];
_someInt = [coder decodeIntForKey:IntKey];
_someString = [coder decodeObjectForKey:StringKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.someBool forKey:BoolKey];
[aCoder encodeInt:self.someInt forKey:IntKey];
[aCoder encodeObject:self.someString forKey:StringKey];
}
#end
// Somewhere in your app where reading and saving game state is needed...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = nil;
if ([paths count]) {
documentsDirectory = paths[0];
}
NSString *archivePath = [documentsDirectory stringByAppendingPathComponent:#"archive"];
GameState *gameState = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];
if (!gameState) {
gameState = [[GameState alloc] init];
gameState.someString = #"a string";
gameState.someInt = 42;
gameState.someBool = YES;
}
// Make changes to gameState here...
[NSKeyedArchiver archiveRootObject:gameState toFile:archivePath];
The first problem is when you test if (boolean) it's the same as saying if (boolean == YES). Bools aren't objects, and can't be nil. When you pass nil in as a bool, it's the same as passing NO in. I don't think this accounts for all of your issues though. I think the file is not saving as well.
From the NSKeyedUnarchiver docs:
If you invoke one of the decode... methods of this class using a key
that does not exist in the archive, a non-positive value is returned.
This value varies by decoded type. For example, if a key does not
exist in an archive, decodeBoolForKey: returns NO, decodeIntForKey:
returns 0, and decodeObjectForKey: returns nil.
These are the erroneous values you're getting. To start, I note that you're not doing any error checking. Try adding some checks to see what's failing, for example, you could try:
[encoder finishEncoding];
NSError *error;
BOOL success = [gameData writeToFile:gameStatePath options:NSDataWritingAtomic error:&error];
if (success == NO) NSLog(#"Error: %#", [error localizedDescription]);
Once you get an error, we can go from there.

Resources