Is it possible to store multiple objects using object archving? - iphone

Okay, of course it is possible, that's no issue. In my code I have multiple objects that I work with, and they need to be data persistent. To do that I've used object encoding and storing them in a data file. Such as:
-(NSString *)dataFilePath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:kFilename];
}
And then further along in another section of code:
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSLog(#"Test 4");
[archiver encodeObject:playerTwo forKey:#"PlayerTwoObject"];
[archiver encodeObject:singlePlayer forKey:#"SinglePlayer"];
[archiver encodeObject:playerOne forKey:#"PlayerOneObject"];
[archiver encodeObject:settings forKey:#"Settings"];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
[singlePlayer release];
[playerOne release];
[playerTwo release];
[settings release];
And that saves the objects. I know it works, because I can take them out with this code:
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:[[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]]];
SettingsObject *settings = [unarchiver decodeObjectForKey:#"Settings"];
Player *test = [unarchiver decodeObjectForKey:#"PlayerOneObject"];
NSLog(#"Power On Settings Test: %d,%d", [settings playerOneStartingLife], [settings singlePlayerStartingLife]);
NSLog(#"Power On Player Test: %#,%d", [test name], [test life]);
Cool! So now I can store my objects, and read them out. So this is where the issue comes in. In a different class I take out one of the objects I'm storing and work with it. Not all of them, I only read out one. And then I save it again after I change the settings. But I only save it:
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:singlePlayer forKey:#"SinglePlayer"];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
[singlePlayer release];
After I have done that, all the rest of the objects I have stored are removed. Now I'm confused, because I thought I was only editing the one key "SinglePlayer", but apparently that somehow removes the rest of them? Is that supossed to happen? I have had some issues with releasing the archiver, would that affect it at all... I don't think it would. Does anyone have some insight on this, because I don't want to have to take all of the objects out and re-encode them if I only want to edit one of them...
Thanks for your help!

You create a new archiver, with an emtpy data object. You write the SinglePlayer object in the data, but you do not store the rest of the data in the same, newly created archive.
To do so, unarchive the whole previously stored archive, then adjust the SinglePlayer object and archive and save it again.

Related

Crashed while using NSKeyedArchiver

I have a NSMutableArray and NSString I want to Archive these and also want to retrieve array and string with out change data inside the array and string.
I given like this,
for Archive,
NSMutableData *data = [[NSMutableData alloc]init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:arrayRandomNumberForUser forKey:#"array"];
[archiver encodeObject:stringBingo forKey:#"string"];
[archiver finishEncoding];
for UnArchive,
NSData *data=match.matchData;
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
mutableArray = [unarchiver decodeObjectForKey:#"array"];
stringtakenValue = [unarchiver decodeObjectForKey:#"string"];
[unarchiver finishDecoding];
Where, match.matchData is an NSData object getting from gamecenter take turn method.
When called the take turnMethod it is crashed. Here is the Console window error message.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: ' -[NSXPCEncoder encodeObject:forKey:]: This coder only encodes objects that adopt NSSecureCoding (object is of class 'NSKeyedArchiver').'
I not sure but I think unarchiver returns an NSArray, not an NSMutableArray. So try
mutableArray = [[unarchiver decodeObjectForKey:#"array"] mutableCopy];

Passing archived Data through a sharedURL

I currently have two somewhat similar apps and was looking into a way to share data across the two apps. I set up a shared url and everything and can jump from one app to the other without any problem. The problem arises when I try to pass data through the url. I have a class in both apps that are identical to one another and am relying on a NSKeyedArchiver and decoder for the encoding of them and decoding as well as encoding them in strings to pass along the url.
Having access to both sides I know that the data passed from app 1 to app 2 is untainted and arrives just as app 1 encoded it. But I can't seem to decode the data on the other side!
I encode it here:
NSURL* app2=[NSURL URLWithString:#"app2://"];
if ([[UIApplication sharedApplication] canOpenURL:app2]) {
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:[tableHelper objectAtIndex:[indexPath section]]forKey:#"TempData" ];
[archiver finishEncoding];
NSString* tmp=[[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding ] autorelease];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:#"app2://%#",[tmp stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];
[data release];
[archiver release];
}
I then decode it as such:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
if (!url) {
return NO;
}
NSString *URLString = [url absoluteString];
NSString* dataString=[[URLString componentsSeparatedByString:#"app2://"] lastObject];
NSString* perString=[dataString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSKeyedUnarchiver* unArchiver=[[NSKeyedUnarchiver alloc] initForReadingWithData:[perString dataUsingEncoding:NSASCIIStringEncoding]];
TempItem* temp=[unArchiver decodeObjectForKey:#"TempData"];
[unArchiver finishDecoding];
//...//
}
Is there more to encoding/decoding custom classes that I am missing? I do implement NSCoding in my class the problem I think is in
[perstring dataUsingEncoding:NSASCIIStringEncoding]
as it appears to always be null?
Thanks!
I checked the output after the encoding and it is as such
bplist00Ô,-T$topX$objectsX$versionY$archiverÑXTempData©
'U$nullÖ
XitemsizeXquantityUpriceV$classYconvertedTunitQ1#?ð
and after adding percent escapes
bplist00%C3%94%01%02%03%04%05%08,-T$topX$objectsX$versionY$archiver%C3%91%06%07XTempData%C2%80%01%C2%A9%09%0A%17%18%19%1A%1B%1F'U$null%C3%96%0B%0C%0D%0E%0F%10%11%12%13%14%15%16XitemsizeXquantityUpriceV$classYconvertedTunit%C2%80%03%C2%80%05%C2%80%04%C2%80%08%C2%80%06%C2%80%02Q1%23?%C3%B0%00%00%00%00%00%00%23?%C3%B0%00%00%00%00%00%00%10%01%C3%92%1C%0E%1D%1EZNS.objects%C2%A0%C2%80%07%C3%92%20!%22&X$classesZ$classname%C2%A3%23$%25%5ENSMutableArrayWNSArrayXNSObject%5ENSMutableArray%C3%92%20!(+%C2%A2)*XTempItemXNSObjectXTempItem%12%00%01%C2%86%C2%A0_%10%0FNSKeyedArchiver%00%08%00%11%00%16%00%1F%00(%002%005%00%3E%00#%00J%00P%00%5D%00f%00o%00u%00%7C%00%C2%86%00%C2%8B%00%C2%8D%00%C2%8F%00%C2%91%00%C2%93%00%C2%95%00%C2%97%00%C2%99%00%C2%A2%00%C2%AB%00%C2%AD%00%C2%B2%00%C2%BD%00%C2%BE%00%C3%80%00%C3%85%00%C3%8E%00%C3%99%00%C3%9D%00%C3%AC%00%C3%B4%00%C3%BD%01%0C%01%11%01%14%01%1D%01&%01/%014%00%00%00%00%00%00%02%01%00%00%00%00%00%00%00.%00%00%00%00%00%00%00%00%00%00%00%00%00%00%01F
I added NSLogs on the decode portion and it is the same. I am baffled...
After fixing the misplacement of the [unArchiver finishdecoding]; I now get the error
*** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL

NSString read/write to file

Im just having trouble reading and writing NSString to a dat file on the iphone.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *saveFile = [documentsDirectory stringByAppendingPathComponent:#"Profiles.dat"];
Boolean saveFileExists = [[NSFileManager defaultManager] fileExistsAtPath:saveFile];
// NSString *saveFile = [documentsDirectory stringByAppendingPathComponent:#"Profiles.dat"];
NSMutableData *gameData;
NSKeyedUnarchiver *decoder;
NSKeyedArchiver *encoder;
//decoder = [[NSKeyedUnarchiver alloc] initForWritingWithMutableData:gameData];
myGameState *gameState = [myGameState sharedMySingleton];
if(saveFileExists) {
gameData = [NSData dataWithContentsOfFile:saveFile];
decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:gameData];
gameState.Profile1 = [decoder decodeObjectForKey:#"Name1"];
gameState.Profile2 = [decoder decodeObjectForKey:#"Name2"];
gameState.Profile3 = [decoder decodeObjectForKey:#"Name3"];
gameState.Profile4 = [decoder decodeObjectForKey:#"Name4"];
gameState.Profile5 = [decoder decodeObjectForKey:#"Name5"];
gameState.curProfile = [decoder decodeObjectForKey:#"curProfile"];
[decoder release];
}
else {
gameData = [NSMutableData data];
encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:gameData];
gameState.Profile1 = #"Default1";
gameState.Profile2 = #"Default2";
gameState.Profile3 = #"Default3";
gameState.Profile4 = #"Default4";
gameState.Profile5 = #"Default5";
[encoder encodeObject:#"Default1" forKey:#"Name1"];
[encoder encodeObject:#"Default2" forKey:#"Name2"];
[encoder encodeObject:#"Default3" forKey:#"Name3"];
[encoder encodeObject:#"Default4" forKey:#"Name4"];
[encoder encodeObject:#"Default5" forKey:#"Name5"];
gameState.curProfile = gameState.Profile1;
[encoder encodeObject:gameState.curProfile forKey:#"curProfile"];
[encoder finishEncoding];
//[gameData writeToFile:<#(NSString *)path#> atomically:<#(BOOL)useAuxiliaryFile#>
[gameData writeToFile:saveFile atomically:YES ];
//[gameData writeToFile:saveFile atomically:NO encoding:NSStringEncodingConversionAllowLossy error:nil];
[encoder release];
}
gameState.curProfile etc are all NSString pointers. Now im very new to Objective C and just cant seem to get this working correctly. I have searched around forums and cant seem to find the correct way to do this. Literally all i want is to read a write a NSString to a dat file but it aint working as is. I have managed to write ints and bools with no problems just cant get past the text problem on my own.
any help is appreciated
thanks
g
Your code works without any problem here. I guess you have problems in other parts of codes.
By the way, if you only need to save a few entries, you don't have to do all these works yourself; just use NSUserDefaults. It automatically prepares the file to save the data, encodes the data, and decodes the data on the next launch. See this Apple document. It can be used as
[[NSUserDefaults standardUserDefaults] setString:#"boo" forKey:#"key!"];
and later the data can be read as
NSString* s=[[NSUserDefaults standardUserDefaults] stringForKey:#"key!"];
It's as easy as this! You might want to call
[[NSUserDefaults standardUserDefaults] synchronize];
to force the saving of the data to the file; usually this isn't necessary (The system does this automatically when quit, etc.)

Trouble decoding with NSKeyedUnarchiver

I am writing an app targeted at iOS 4.0 on XCode 3.2.3 where I store some data when the app closes using NSCoder protocol. Saving seems to work fine, the problem is retrieving the data from the saved files. My save method looks like this:
-(void)saveMusicalWorksToMemory{
// create the save paths
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *mwPath = [documentsDirectory stringByAppendingPathComponent:kMusicalWorksLocalFileSuffix];
NSString *fwPath = [documentsDirectory stringByAppendingPathComponent:kFeaturedWorksLocalFileSuffix];
NSMutableData *musicalWorksData;
NSMutableData *featuredWorksData;
NSKeyedArchiver *mwEncoder;
NSKeyedArchiver *fwEncoder;
musicalWorksData = [NSMutableData data];
featuredWorksData = [NSMutableData data];
mwEncoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:musicalWorksData];
fwEncoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:featuredWorksData];
// encode the objects
[mwEncoder encodeObject:musicalWorks forKey:kMusicalWorksArchiveKey];
[fwEncoder encodeObject:featuredWorks forKey:kFeaturedWorksArchiveKey];
[mwEncoder finishEncoding];
[fwEncoder finishEncoding];
// write to files
[musicalWorksData writeToFile:mwPath atomically:YES];
[featuredWorksData writeToFile:fwPath atomically:YES];
[mwEncoder release];
[fwEncoder release];
}
And here is the method where I read from the files:
-(void)loadMusicalWorksFromMemory{
// get the path to the locally saved data
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *mwPath = [documentsDirectory stringByAppendingPathComponent:kMusicalWorksLocalFileSuffix];
NSString *fwPath = [documentsDirectory stringByAppendingPathComponent:kFeaturedWorksLocalFileSuffix];
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:mwPath] && [fileManager fileExistsAtPath:fwPath]){
NSMutableData *mwData;
NSMutableData *fwData;
NSKeyedUnarchiver *mwDecoder;
NSKeyedUnarchiver *fwDecoder;
NSMutableArray *tempMusicalWorks;
NSMutableArray *tempFeaturedWorks;
// unarchive the file data
mwData = [NSData dataWithContentsOfFile:mwPath];
fwData = [NSData dataWithContentsOfFile:fwPath];
mwDecoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:mwData];
fwDecoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:fwData];
tempMusicalWorks = [mwDecoder decodeObjectForKey:kMusicalWorksArchiveKey];
tempFeaturedWorks = [fwDecoder decodeObjectForKey:kFeaturedWorksArchiveKey];
[mwDecoder finishDecoding];
[fwDecoder finishDecoding];
[mwDecoder release];
[fwDecoder release];
// restore the content
[self setMusicalWorks:tempMusicalWorks];
[self setFeaturedWorks:tempFeaturedWorks];
}else{
NSLog(#"MusicalWorkManager::loadMusicalWorksFromMemory - No file found at given path");
}
}
The weird thing is that when reading the data from memory and debugging, I see that mwData and fwData are in fact not nil, and have roughly the right size proportions to each other (about 2:1). The problem is though that the decoded arrays (tempMusicalWorks and tempFeaturedWorks) have zero entries (not nil though). This leads me to believe that the problem is actually in the decoding.
For the record, I have also checked that items are being saved in the saveMusicalWorksToMemory() method. The arrays I am passing in through this method are indeed populated with stuff. Also, I have checked that I am implementing the initWithCoder() and encodeWithCoder() methods properly for the objects being encoded and decoded.
Any hints? I'm really lost on this one.
Thanks!
-Matt

Objective-c way of serializing arrays that contain different types

My NSMutableArray instance contains instances of different types of objects that have common ancestor. I want to serialize the array, but NSKeyedArchiver class seems to archive arrays contain certain types of objects. Is there an easy way to to this with stock serialization classes ?
I don't think NSKeyedArchiver is your problem, since it doesn't know about arrays or anything. Perhaps you need to implement the NSCoding protocol on each subclass?
Edit:
Not sure how you're using your NSKeyedArchiver, but this is how you should use it:
NSMutableData *data = [NSMutableData new];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:array forKey:#"array"];
[data writeToFile:filename attomically:YES];
[archiver release];
[data release];
And to read it in later:
NSData *data = [[NSData alloc] initWithContentsOfFile:filename];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
array = [[unarchiver objectForKey:#"array"] retain];
[unarchiver release];
[data release];
This assumes that array is some type of NSObject, including NSArray, NSSet, NSDictionary, etc, or any of their mutable equivalents.