Reputation: 638
I have an object that I convert into NSData using an NSKeyedArchiver and then store it into NSUserDefaults. Everything gets saved correctly except for the elements of an array that the object has. All the objects in the array have conform to the NSCoder protocols (or whatever theyre called- ex. self.property = [decoder decodeObjectForKey:@"key"] and [encoder encodeObjectForKey:@"key"]
)
When I save the object, the elements of the array remain in the array, but their properties themselves do not get saved. i DO call the sycnrhonize
method, so that is not the issue.
NOTE that all other times i save & load it is correct, it just does not save the elements of an array that belongs to an object. Do i have to save that separately?
The "current status" NSNumber is not being saved. Objective and target are being saved
@implementation Level
@synthesize objective = _objective;
@synthesize isComplete = _isComplete;
@synthesize goldReward = _goldReward;
@synthesize xpReward = _xpReward;
@synthesize missionID = _missionID;
@synthesize currentStatus = _currentStatus;
@synthesize targetName = _targetName;
@synthesize owner = _owner;
-(void)dealloc{
[super dealloc];
}
-(id)initWithMissionID:(int)number{
if (self = [super init]) {
self.currentStatus = 0;
self.isComplete = NO;
self.missionID = [NSNumber numberWithInt:number];
[self setUpMisson];
}
return self;
}
-(void)setUpMisson{
if ([self.missionID intValue] == 0) {
self.xpReward = [NSNumber numberWithInt:100];
self.goldReward = [NSNumber numberWithInt:100];
self.objective = [NSNumber numberWithInt:3];
self.targetName = @"Swordsman";
CCLOG(@"Gotta kill some swordsmen!");
}
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.objective forKey:@"objective"];
[encoder encodeObject:self.isComplete forKey:@"isComplete"];
[encoder encodeObject:self.goldReward forKey:@"goldReward"];
[encoder encodeObject:self.xpReward forKey:@"xpReward"];
[encoder encodeObject:self.missionID forKey:@"missionID"];
[encoder encodeObject:self.currentStatus forKey:@"currentStatus"];
[encoder encodeObject:self.targetName forKey:@"targetName"];
[encoder encodeObject:self.owner forKey:@"owner"];
CCLOG(@"SAVING LEVEL");
}
-(id)initWithCoder:(NSCoder *)decoder{
if (self = [super init]) {
self.objective = [[decoder decodeObjectForKey:@"objective"]retain];
self.isComplete = [[decoder decodeObjectForKey:@"isComplete"]retain];
self.goldReward = [[decoder decodeObjectForKey:@"goldReward"]retain];
self.xpReward = [[decoder decodeObjectForKey:@"xpReward"]retain];
self.missionID = [[decoder decodeObjectForKey:@"missionID"]retain];
self.targetName = [[decoder decodeObjectForKey:@"targetName"]retain];
self.owner = [[decoder decodeObjectForKey:@"owner"]retain];
CCLOG(@"LOADING LEVEL");
}
return self;
}
-(void)updateStatusForKill:(AI *)killedTarget{
CCLOG(@"WE KILLED: %@ and OUR GOAL IS: %@",killedTarget.name,self.targetName);
if ([killedTarget.name isEqualToString:self.targetName]) {
[self setCurrentStatus:[NSNumber numberWithInt:[self.currentStatus intValue]+1]];
CCLOG(@"Current Status: %i Objective: %i", [self.currentStatus intValue],[self.objective intValue]);
if ([self.currentStatus intValue] == [self.objective intValue]) {
[self completeMission];
}
}
}
-(void)completeMission{
[self.owner setCoins:[NSNumber numberWithInt:[[self.owner coins]intValue] + [self.goldReward intValue]]];
[self.owner setXp:[NSNumber numberWithInt:[[self.owner xp]intValue] + [self.xpReward intValue]]];
CCLOG(@"complete");
[[self.owner missionList]removeObject:self];
}
@end
EDIT: The "owner" refers back to the object being saved. I think this is where the problem is, so I'm removing that and testing again.
EDIT: and that did nothing!
Upvotes: 0
Views: 704
Reputation: 253
I ran into this problem when trying to save an array of Accounts that contain property values and another custom object. I couldn't save my data with your proposed solution because I was arbitrarily adding accounts to an array, and it wouldn't make sense to come up with unique identifiers for dynamically added accounts. I ended up nesting the NSCoding protocol:
In my AccountManager
class:
- (void) saveAllAccounts {
[self deleteAllAccounts]; //Just removes whatever was previously stored there
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:accountArray] forKey:saveAccountsArrayKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
In my Account
class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:username forKey:@"username"];
[aCoder encodeObject:token forKey:@"token"];
[aCoder encodeObject:specialID forKey:@"special ID"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:deviceArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"device array"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
username = [[aDecoder decodeObjectForKey:@"username"] retain];
token = [[aDecoder decodeObjectForKey:@"token"] retain];
ecoID = [[aDecoder decodeObjectForKey:@"eco ID"] retain];
NSData *deviceArrayData = [[NSUserDefaults standardUserDefaults] objectForKey:@"device array"];
if (deviceArrayData != nil) {
NSArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData: deviceArrayData];
if (savedArray != nil)
deviceArray = [[NSMutableArray alloc] initWithArray:savedArray];
}
}
return self;
}
In my AccountDevice
class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:pairingPassword forKey:@"pairing password"];
[aCoder encodeObject:devicePin forKey:@"device pin"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
password = [[aDecoder decodeObjectForKey:@"password"] retain];
pin = [[aDecoder decodeObjectForKey:@"pin"] retain];
}
return self;
}
It might be a little buggy because I haven't finished testing it, but my preliminary tests were successful, and I think the concept is there.
Upvotes: 1
Reputation: 638
found a workaround using a Unique-Id system for each mission and saving the progress for each mission separately into NSUserDefaults which are just then loaded again. Not ideal, but it works. Thanks for everyone's help!
Upvotes: 0
Reputation: 125017
It looks like you're using -encodeObject:forKey:
and -decodeObjectForKey:
even on values that aren't objects. For example, in your -initWithMissionID:
you've got:
self.isComplete = NO;
which makes me think that complete
is a BOOL
property, but your -encodeObject:forKey:
says:
[encoder encodeObject:self.isComplete forKey:@"isComplete"];
I think you probably want to call -encodeBool:forKey:
instead, like this:
[encoder encodeBool:self.isComplete forKey:@"isComplete"];
On the other hand, I'd really expect some sort of warning if the problem were that simple. Do you get any warnings? It's harder to infer the types of your other properties, but look at each of your properties for the same kind of problem.
Upvotes: 0
Reputation: 131491
What you describe should "just work." In the encodeWithCoder method of your custom object, you would just encode the array object, and that should cause the array and it's contents to be encoded.
However, if any of the objects in the array do not support NSCoding, that will fail. My guess is that something in your array (or it's children or grandchildren) does not support NSCoding.
Upvotes: 1