iPhone Serialization problem - iphone

I need to save my own created class to file, I found on the internet, that good approach is to use NSKeyedArchiver and NSKeyedUnarchiver
My class definition looks like this:
#interface Game : NSObject <NSCoding> {
NSMutableString *strCompleteWord;
NSMutableString *strWordToGuess;
NSMutableArray *arGuessedLetters; //This array stores characters
NSMutableArray *arGuessedLettersPos; //This array stores CGRects
NSInteger iScore;
NSInteger iLives;
NSInteger iRocksFallen;
BOOL bGameCompleted;
BOOL bGameOver;
}
I've implemented methods initWithCoder: and encodeWithCoder: this way:
- (id)initWithCoder:(NSCoder *)coder
{
if([coder allowsKeyedCoding])
{
strCompleteWord = [[coder decodeObjectForKey:#"CompletedWord"] copy];
strWordToGuess = [[coder decodeObjectForKey:#"WordToGuess"] copy];
arGuessedLetters = [[coder decodeObjectForKey:#"GuessedLetters"] retain];
// arGuessedLettersPos = [[coder decodeObjectForKey:#"GuessedLettersPos"] retain];
iScore = [coder decodeIntegerForKey:#"Score"];
iLives = [coder decodeIntegerForKey:#"Lives"];
iRocksFallen = [coder decodeIntegerForKey:#"RocksFallen"];
bGameCompleted = [coder decodeBoolForKey:#"GameCompleted"];
bGameOver = [coder decodeBoolForKey:#"GameOver"];
}
else
{
strCompleteWord = [[coder decodeObject] retain];
strWordToGuess = [[coder decodeObject] retain];
arGuessedLetters = [[coder decodeObject] retain];
// arGuessedLettersPos = [[coder decodeObject] retain];
[coder decodeValueOfObjCType:#encode(NSInteger) at:&iScore];
[coder decodeValueOfObjCType:#encode(NSInteger) at:&iLives];
[coder decodeValueOfObjCType:#encode(NSInteger) at:&iRocksFallen];
[coder decodeValueOfObjCType:#encode(BOOL) at:&bGameCompleted];
[coder decodeValueOfObjCType:#encode(BOOL) at:&bGameOver];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
if([coder allowsKeyedCoding])
{
[coder encodeObject:strCompleteWord forKey:#"CompleteWord"];
[coder encodeObject:strWordToGuess forKey:#"WordToGuess"];
[coder encodeObject:arGuessedLetters forKey:#"GuessedLetters"];
//[coder encodeObject:arGuessedLettersPos forKey:#"GuessedLettersPos"];
[coder encodeInteger:iScore forKey:#"Score"];
[coder encodeInteger:iLives forKey:#"Lives"];
[coder encodeInteger:iRocksFallen forKey:#"RocksFallen"];
[coder encodeBool:bGameCompleted forKey:#"GameCompleted"];
[coder encodeBool:bGameOver forKey:#"GameOver"];
}
else
{
[coder encodeObject:strCompleteWord];
[coder encodeObject:strWordToGuess];
[coder encodeObject:arGuessedLetters];
//[coder encodeObject:arGuessedLettersPos];
[coder encodeValueOfObjCType:#encode(NSInteger) at:&iScore];
[coder encodeValueOfObjCType:#encode(NSInteger) at:&iLives];
[coder encodeValueOfObjCType:#encode(NSInteger) at:&iRocksFallen];
[coder encodeValueOfObjCType:#encode(BOOL) at:&bGameCompleted];
[coder encodeValueOfObjCType:#encode(BOOL) at:&bGameOver];
}
}
And I use these methods to archive and unarchive data:
[NSKeyedArchiver archiveRootObject:currentGame toFile:strPath];
Game *currentGame = [NSKeyedUnarchiver unarchiveObjectWithFile:strPath];
I have two problems.
1) As you can see, lines with arGuessedLettersPos is commented, it's because every time I try to encode this array, error comes up(this archiver cannot encode structs), and this array is used for storing CGRect structs.
I've seen solution on the internet. The thing is, that every CGRect in the array is converted to an NSString (using NSStringFromCGRect()) and then saved. Is it a good approach?
2)This is bigger problem for me. Even if I comment this line and then run the code successfully, then save(archive) the data and then try to load (unarchive) them, no data is loaded. There aren't any error but currentGame object does not have data that should be loaded.
Could you please give me some advice? This is first time I'm using archivers and unarchivers.
Thanks a lot for every reply.

The problem with loading and saving solved another way...
Instead of implementing - (id)initWithCoder:(NSCoder )coder and - (void)encodeWithCoder:(NSCoder)coder I used this solution:
NSMutableData *data = [NSMutableData alloc];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.strCompleteWord forKey:#"CompleteWord"];
[archiver encodeObject:self.strWordToGuess forKey:#"WordToGuess"];
[archiver encodeObject:self.arGuessedLetters forKey:#"GuessedLetters"];
//[coder encodeObject:self.arGuessedLettersPos forKey:#"GuessedLettersPos"];
[archiver encodeInteger:self.iScore forKey:#"Score"];
[archiver encodeInteger:self.iLives forKey:#"Lives"];
[archiver encodeInteger:self.iRocksFallen forKey:#"RocksFallen"];
[archiver encodeBool:self.bGameCompleted forKey:#"GameCompleted"];
[archiver encodeBool:self.bGameOver forKey:#"GameOver"];
[archiver finishEncoding];
[data writeToFile:strPath atomically:YES];
[data release];
and
NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile:strPath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self.strCompleteWord = [[unarchiver decodeObjectForKey:#"CompletedWord"] copy];
self.strWordToGuess = [[unarchiver decodeObjectForKey:#"WordToGuess"] copy];
self.arGuessedLetters = [[unarchiver decodeObjectForKey:#"GuessedLetters"] retain];
//self.arGuessedLettersPos = [[unarchiver decodeObjectForKey:#"GuessedLettersPos"] retain];
self.iScore = [unarchiver decodeIntegerForKey:#"Score"];
self.iLives = [unarchiver decodeIntegerForKey:#"Lives"];
self.iRocksFallen = [unarchiver decodeIntegerForKey:#"RocksFallen"];
self.bGameCompleted = [unarchiver decodeBoolForKey:#"GameCompleted"];
self.bGameOver = [unarchiver decodeBoolForKey:#"GameOver"];
[unarchiver finishDecoding];
[data release];
And this works totally fine :)

I might be missing it, but I don't see any obvious bugs in this code.
Here are some ideas that might help:
Add some NSLog statements, and watch the debug output (open with command-shift-R in xcode) to see if your encode/decode methods are actually being called.
Check that the archive file is saved: Running in the simulator, you can save to any local path you want, such as /tmp/my_archive_file. Try to save to that file, and see if (a) the file exists with the right timestamp, and (b) you print out the file, you can see some recognizable strings (like "RocksFallen") in amongst the binary gooblygoo.
I also don't think it's necessary to check for allowsKeyed(En)coding since you know that's always going to be true when you're explicitly using NSKeyed(Un)archiver to do your dirty work for you. So you can throw away the other half of your code.
About coding those arrays of CGRects: I don't think you can directly add structs to an NSMutableArray, right? So they must be some kind of object, which means you can add NSCoding support to that object. If it's not your own object, you can subclass it and add that protocol, or try adding it as a category.

Thanks for reply Tyler.
I looked in the saved file and there are recognizable strings like RocksFallen and so on, also there are some recognizable values that might be saved in string variables.. So this seems to be good.
I tried to put some NSLogs in the code and not everything seems to be good. So...
When I launch simulator for the first time, there's no archive file, yes it is obvious, because nothing is saved. Then I change something and exit the application. When the application is being terminated, data might be archived, everything's fine. NSLog statements are written in the console, file is on the disk. But what is strange is that when I launch the application then, decode method(initwithcoder) is not being called.. Don't know why. Then I exit the simulator and run the simulator again. When I run it again, decode method is being called at the launchtime. But only at the first launchtime after running simulator, when I then work with simulator like exit the app and run it again, initWithCoder method is not being called and that's very strange for me..
So there are problems unarchiving it i think.

The best way to archive structs (that are not holding any pointers) with would be to wrap the values in an NSData block.
here, i'm dumping a struct array to the archiver:
for (int i = 0; i < items; i++) {
NSString *keyValue = [NSString stringWithFormat:#"YourStruct%i", i ];
NSData *dataset = [NSData dataWithBytes:(void*)&activeprofile[i] length:(sizeof(profileset))];
[encoder encodeObject:dataset forKey:keyValue]; // makes a nice, solid data block
}
and reading the data back in:
for (int i = 0; i < items; i++) {
NSString *keyValue = [NSString stringWithFormat:#"YourStruct%i", i ];
NSData *dataset = [decoder decodeObjectForKey:keyValue];
[dataset getBytes:&activeprofile[i] length:sizeof(profileset)];
}
There you have it: easy as pie and extremely flexible with datatypes!
A similar method for non-keyed archiving can be found here: http://borkware.com/quickies/one?topic=NSCoder Though I suggest sticking with keyed archiving, as NSArchive is not supported on the iPhone and has been classified as legacy code.

Related

iPhone - Saving data to a plist file

I've been following this guide on saving data to a plist, using NSCoding. I've followed the guide step by step as far as i can tell, as well as looked at the sample code, but when it comes to actually saving the data, it doesn't seem to save it, nor does it seem to call the save function. I'm calling the save function via a button:
- (IBAction)saveAll:(id)sender {
NSLog(#"start saveall");
_conosirDoc.data.wineType = _wineType.text;
_conosirDoc.data.wineTitle = _wineTitle.text;
_conosirDoc.data.wineYear = _wineYear.text;
_conosirDoc.data.wineVolume = _wineVolume.text;
_conosirDoc.data.wineRating = _wineRating.text;
_conosirDoc.data.wineCountry = _wineCountry.text;
_conosirDoc.data.wineRegion = _wineRegion.text;
_conosirDoc.data.wineGrapes = _wineGrapes.text;
[_conosirDoc saveData];
NSLog(#"%#", _conosirDoc.data.wineType);
NSLog(#"end saveall");
}
so when the button is clicked the log reads :
start saveall
(null)
end saveall
so from there I set up the following NSLog's in the saveData function:
- (void)saveData {
NSLog(#"being called?");
if (_data == nil) return;
NSLog(#"START");
[self createDataPath];
NSLog(#"END");
NSString *dataPath = [_docPath stringByAppendingPathComponent:kDataFile];
NSLog(#"%#", dataPath);
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:_data forKey:kDataKey];
[archiver finishEncoding];
[data writeToFile:dataPath atomically:YES];
}
and from this, none of these NSLogs appear in the output, and i can't work out why it's not being called properly. Let me know if you need anymore information.
Thanks.
The log lines that you see (and especially the ones that you do not see) indicate that _conosirDoc is nil - it has not been initialized: the only reason the saveData would not log the unconditional "being called?" is that saveData is invoked on a nil. In all other cases there would be at least that additional log line.
You need to add code that assigns an object to your _conosirDoc variable to make it work:
_conosirDoc = [[ConosirDocType alloc] init];
Replace ConosirDocType above with the actual type of _conosirDoc.

asyncSocket writeData crashes with different NSData

If I use this to set up NSData then the writeData method crashes.
NSString *test = #"The quick brown fox jumped over the lazy dog\r\n";
NSData *data = [test dataUsingEncoding:NSUTF8StringEncoding];
[asyncSocket writeData:data withTimeout:10 tag:4];
However if I use this one then it works... but I need the NSString so I can enter a formatted string to send...
char bytes[] = "The quick brown fox jumped over the lazy dog\r\n";
NSData* data = [[NSData alloc] initWithBytes:bytes length:sizeof(bytes)];
[asyncSocket writeData:data withTimeout:10 tag:4];
So what did I do wrong?
The NSString and the NSDate were not setup with alloc and init so they were gone when they got to the write data. I changed the NSDate to alloc and init and all works well now. These idea came from the several people that answered this. thanks for the help!
The latter is null terminated, the former is not. This is probably the issue.
I know its bit late but I thought it would be good to know why this works - When you call writeData method with your data buffer, the asyncsocket code creates a new object by sublassing NSObject - AsyncWritePacket using the data passed down. It then uses retain and takes ownership of the data buffer you are passing hence why you dont want to release it.
#implementation AsyncWritePacket
- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
{
if((self = [super init]))
{
buffer = [d retain];
timeout = t;
tag = i;
bytesDone = 0;
}
return self;
#end

Array doesn't persist outside viewDidLoad

I'm facing a strange problem with NSUsrDefaults. Whenever I'm fetching the data from NSUserDefaults, it's getting populated temporarily. I'm fetching it into viewDidLoad where it's fetched.
-(void)viewDidLoad{
companies = [NSMutableArray array];
oldCompanies = [[NSUserDefaults standardUserDefaults] arrayForKey:#"companyData"];
if( companies )
{
for( NSData *data in oldCompanies )
{
companyObj = (Company*) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[companies addObject:companyObj];
}
}
}
But outside viewDidLoad, whenever I try to access the data, the array "oldCompanies" as well as "companies" are shown "nil".
EDIT:
I'm encoding my Company object in a class which subclasses NSCoding like shown below but not allocating or retaining the properties anywhere. Can this be the catch?
- (void)encodeWithCoder:(NSCoder *)encoder
{
//Encode properties, other class variables, etc
[encoder encodeObject:self.companyId forKey:#"id"];
[encoder encodeObject:self.companyTitle forKey:#"title"];
[encoder encodeObject:self.companyImage forKey:#"image"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if( self != nil )
{
//decode properties, other class vars
self.companyId = [decoder decodeObjectForKey:#"id"];
self.companyTitle = [decoder decodeObjectForKey:#"title"];
self.companyImage = [decoder decodeObjectForKey:#"image"];
}
return self;
}
Can anybody please help?
Thanx in advance.
+array creates an autoreleased array - if you want to take ownership of it per the memory management rules then you need to retain it:
[companies retain];
Or create it so that it isn't autoreleased:
companies = [[NSMutableArray alloc] init];
Or, better, let declared properties do that for you:
// interface, property declaration:
#property(retain, readwrite) NSMutableArray *companies;
// implementation:
#synthesize companies;
// ... using it:
self.companies = [NSMutableArray array];
You are not retaining the array, when you dont do an alloc, or a retain when instatiating an object you get an autoreleased object, in your example companies is autoreleased and is why you cant access it anymore at a later point you should either alloc it
[[NSMutableArray alloc] init] or retain it [NSMutableArray array] retain]...either way refer to memory managment guide to learn about objective-c memory managment memory managment ref
Are you trying to access the data before the view is loaded?
Objective-C doesn't reset your pointers for you. If the array isn't "persisted", then the pointer will point to garbage.

Static variable for communication among like-typed objects

I have a method that asynchronously downloads images. If the images are related to an array of objects (a common use-case in the app I'm building), I want to cache them. The idea is, I pass in an index number (based on the indexPath.row of the table I'm making by way through), and I stash the image in a static NSMutableArray, keyed on the row of the table I'm dealing with.
Thusly:
#implementation ImageDownloader
...
#synthesize cacheIndex;
static NSMutableArray *imageCache;
-(void)startDownloadWithImageView:(UIImageView *)imageView andImageURL:(NSURL *)url withCacheIndex:(NSInteger)index
{
self.theImageView = imageView;
self.cacheIndex = index;
NSLog(#"Called to download %# for imageview %#", url, self.theImageView);
if ([imageCache objectAtIndex:index]) {
NSLog(#"We have this image cached--using that instead");
self.theImageView.image = [imageCache objectAtIndex:index];
return;
}
self.activeDownload = [NSMutableData data];
NSURLConnection *conn = [[NSURLConnection alloc]
initWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
self.imageConnection = conn;
[conn release];
}
//build up the incoming data in self.activeDownload with calls to didReceiveData...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Finished downloading.");
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
self.theImageView.image = image;
NSLog(#"Caching %# for %d", self.theImageView.image, self.cacheIndex);
[imageCache insertObject:image atIndex:self.cacheIndex];
NSLog(#"Cache now has %d items", [imageCache count]);
[image release];
}
My index is getting through okay, I can see that by my NSLog output. But even after my insertObject: atIndex: call, [imageCache count] never leaves zero.
This is my first foray into static variables, so I presume I'm doing something wrong.
(The above code is heavily pruned to show only the main thing of what's going on, so bear that in mind as you look at it.)
You seem to never initialize the imageCache and probably got lucky with it having the value 0. The initialization would best be done in the class' initialization, e.g.:
#implementation ImageDownloader
// ...
+(void)initialize {
imageCache = [[NSMutableArray alloc] init];
}
// ...

NSKeyedUnarchiver causing memory leak

I have a memory leak problem, I'm saving an array into a file using:
[NSKeyedArchiver archiveRootObject:myArray toFile:MyFile];
the objects included into the array have the following methods:
- (id)initWithCoder:(NSCoder *)coder
{
[super init];
parameter1 = [[coder decodeObject] retain];
parameter2 = [[coder decodeObject] retain];
parameter3 = [[coder decodeObject] retain];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:parameter1];
[coder encodeObject:parameter2];
[coder encodeObject:parameter3];
}
To unarchive the objects I'm using:
myUnarchivedArray = [NSKeyedUnarchiver unarchiveObjectWithFile:myFile];
The application suddenly crashes because the available memory is not enough to continue.
I'm unable to deallocate myUnarchivedArray and the Intruments tool is telling me that the unarchiver is causing the memory leak.
I haven't search too much, I just found the cause of the memory leak, but I was hopping to find someone that has been passed the same problem and has a tip to solve it.:)
Thank you!!
Anna
You're retaining your objects in initWithCoder although the documentation states:
NSKeyedUnarchiver’s implementation, however, returns an autoreleased object, so its life is the same as the current autorelease pool instead of the keyed unarchiver.