Serialize an NSMutableArray with Objects inside - iphone

I have an NSMutableArray with a bunch of Points(it's a class defined by me) inside. And I want to serialize this NSMutableArray to save to my disk. If I implement in my class Point the NSCoding protocol is it possible to use NSKeyedArchiver directly in my NSMutableArray?
Just like this example:
NSMutableData *data = [NSMutableData new];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:myArray forKey:#"array"];
[data writeToFile:filename attomically:YES];
[archiver release];
[data release];

Yes it is possible, quoting Apple documentation :
The NSArray class adopts the NSCopying, NSMutableCopying, and NSCoding protocols;
Note that if you're working with points, maybe you should use the Apple CGPoint structure, which comes with many handy utilities : CGPointFromString and NSStringFromCGPoint see Apple documentation for more information.
And also these others ones might interest you CGPointEqualToPoint, CGRectContainsPoint, CGRectIntersectsRect or CGRectContainsRect... see Apple documentation for more information

Related

NSMutableData Plist for NSMutableDictionary

I am loading a plist via NSURLConnection into NSMutableData.
After that is done I want to read the PLIST into a NSMutableDictionary.
And then add the objects into my array to display them in a tableview.
But at the moment I don't know how to extract the data from NSMutableData into my NSMutableDictionary.
If I save the data local as plist on the iPhone in some folder and then read the plist into my Dictionary it works. But isn't there a way to do this directly?
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
receivedData = [[NSMutableData alloc] initWithData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSData *data = [[NSMutableData alloc] initWithData:receivedData];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSDictionary *myDictionary = [unarchiver decodeObjectForKey:#"Beverage"];
[unarchiver finishDecoding];
beverageArray = [[NSMutableArray alloc] init];
beverageArray = [myDictionary objectForKey:#"Beverage"];
NSLog(#"%#", beverageArray);
}
Before using NSURLConnection I used this which works:
- (void) makeDataBeverage {
beverageArray = [[NSMutableArray alloc] init];
NSMutableDictionary *beverageDic = [[NSMutableDictionary alloc]initWithContentsOfURL:[NSURL URLWithString:NSLocalizedString(#"beverage", nil)]];
beverageArray = [beverageDic objectForKey:#"Beverage"];
Now I want to the same with using NSURLConnection.
Assuming you have the complete data(*), you'll want to look into the NSPropertyListSerialization class. Its +propertyListWithData:options:format:error: method should get you what you're looking for, and you can use the options parameter to get the results as a mutable dictionary or array.
(*)It sounds like you have the complete data, since you say you can write it to a file and then read it in using dictionaryWithContentsOfFile: or similar, but it doesn't look like you're guaranteed to get it from the code you've shown. You're creating a new data in -connection:didReceiveData:, but that delegate method can be called multiple times as the data arrives in pieces. (I'm guessing it just happened to arrive all in one piece for your testing... this may not always be true, especially on a mobile device.) Instead, you probably want to create an empty mutable data when you start your NSURLConnection (or in -connection:didReceiveResponse:), append to it in -connection:didReceiveData:, and parse it in -connectiondidFinishLoading:. Or even better, since the property list parser can't do anything with a partial data anyway, use the new +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] if you're targeting iOS 5.0+.

How do I archive two objects using NSKeyedArchiever?

Previously, I have an array in which I use NSKeyedArchiver to archieve.
[NSKeyedArchiver archiveRootObject:array toFile:docPath];
Called on applicationWillTerminate and applicationDidEnterBackground.
Upon starting up, in didFinishLaunchingWithOPtions, the array is unarchieved:
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:docPath];
Everything was good. Since then, I added a Singleton class for Settings variables. I would like to archive the Singleton as well. How would I do that?
The part that confuses me is if I call the same message:
[NSKeyedArchiver archiveRootObject:singleton toFile:docPath];
How can the app know which object I want to unarchive when I call:
[NSKeyedUnarchiver unarchiveObjectWithFile:docPath];
Is it the array or the singleton?
The archiveRootObject accepts an (id) which to me means whatever I want. I can create a data class which includes the array and the singleton. Is that how I am suppose to do this? Is there a better way? Any suggestions are welcomed.
Thanks!
If you want to encode more than one object at the root level, you'll need to create the archiver using +alloc and -initForWritingWithMutableData: yourself, send it -encodeObject:forKey: messages for each of the objects you want to add to the archive, and finally -finishEncoding. You can then write the data to a file yourself.
It'll look something like this (warning: untested code ahead):
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:someObject forKey:#"people"];
[archiver encodeObject:anotherObject forKey:#"places"];
[archiver encodeObject:thirdObject forKey:#"things"];
[archiver finishEncoding];
[data writeToURL:someUrl atomically:YES];
[archiver release];
To retrieve the objects, you'll do essentially the opposite: read a file into an NSData object; create an NSKeyedUnarchiver with +alloc and -initForReadingWithData:; retrieve the objects you care about using -decodeObjectForKey:; and finally call -finishDecoding. You can almost read the sample above from bottom to top with a few name changes.
To be archivable, your singleton class must conform to the NSCoding protocol. You will have to implement its initWithCoder: and encodeWithCoder: methods.
Again if you do archiveRootObject:toFile: on two objects with the same file path then you will overwrite one of them. To encode two objects, you will have to do something like this,
NSData * data = [NSMutableData data];
NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:array forKey:#"Array"];
[archiver encodeObject:singleton forKey:#"Singleton"];
[archiver finishEncoding];
BOOL result = [data writeToFile:filePath atomically:YES];
[archiver release];
You can read more on archiving in this guide.

How do I archive an NSArray of NSDictionary (with NSCoding)?

Suppose I am holding data in an array like this
wordList = [[NSMutableArray alloc] init];
while ([rs next]) //Some database return loop
{
wordDict = [[NSMutableDictionary alloc] init];
[wordDict setObject:[NSNumber numberWithInt:[rs intForColumn:#"id"]] forKey:#"id"];
[wordDict setObject:[rs stringForColumn:#"word"] forKey:#"word"];
[wordList addObject: wordDict];
[wordDict release];
wordDict = nil;
}
But I want to store this result (i.e. wordList) in SQLite for later use - I guess using NSCoding. How would I do that?
(Feel free to point out any errors in how stuff is being alloc'ed if there are problems there).
If you don’t insist on serialization using NSCoding, there’s a writeToFile:atomically: method both on NSArray and NSDictionary. This will serialize your object into a property list (*.plist). The only catch is that all the objects in the “tree” to be serialized must be NSString, NSData, NSArray, or NSDictionary (see the documentation). I’m not sure how NSNumber fits in, but with a bit of luck it will be serialized and deserialized too. The inverse method that will turn the file back into a dictionary or an array is called initWithContentsOfFile:.
As for your code, I would just use the [NSMutableDictionary dictionary] convenience method that gets you an autoreleased dictionary. It’s shorter than the usual alloc & init and you save one line for the explicit release.

Why is my NSURLResponse leaking memory when archived?

I found a leak in my code where archiving and unarchiving an NSURLResponse was causing a leak, and I can't figure out why.
- (void)doStuffWithResponse:(NSURLResponse *)response {
NSMutableData *saveData = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:saveData];
[archiver encodeObject:response forKey:#"response"];
// Encode other objects
[archiver finishDecoding];
[archiver release];
// Write data to disk
// release, clean up objects
}
- (void)retrieveResponseFromPath:(NSString *)path {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:[NSData dataWithContentsOfFile:path]];
NSURLResponse *response = [unarchiver decodeObjectForKey:#"response"];
// The above line leaks!!
// decode other objects
// clean up memory and do other operations
}
Instruments reports a leak when I unarchive the NSURLResponse. If I comment that out and do not use it, there is no leak. What was interesting was instead I saved the pieces of the NSURLResponse there is no leak:
// Encode:
[archiver encodeObject:[response URL] forKey:#"URL"];
[archiver encodeObject:[response MIMEType] forKey:#"MIMEType"];
[archiver encodeObject:[NSNumber numberWithLongLong:[response expectedContentLength]] forKey:#"expectedContentLength"];
[archiver encodeObject:[response textEncodingName] forKey:#"textEncodingName"];
// Decode:
NSURL *url = [unarchiver decodeObjectForKey:#"URL"];
NSString *mimeType = [unarchiver decodeObjectForIKey:#"MIMEType"];
NSNumber *expectedContentLength = [unarchiver decodeObjectForKey:#"expectedContentLength"];
NSString *textEncodingName = [unarchiver decodeObjectForKey:#"textEncodingName"];
NSURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:url MIMEType:mimeType expectedContentLength:[expectedContentLength longLongValue] textEncodingName:textEncodingName];
Anyone know why this is? Is there a bug with archiving NSURLResponse or am I doing something wrong?
Memory management in Objective-C is as simple as knowing that any time you call something that has "alloc", "new", or "copy" in the method name (or if you retain it), then you must release it at some point. See this for more info: http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
In your case, it appears that you call alloc to create an NSMutableData, but never release it (So [saveData release] at the end of doStuffWithResponse: may resolve at least one leak). From this code, this also appears to be the case with your alloc'ed NSKeyedUnarchiver and your alloc'ed NSURLResponse.
If you're not holding onto the value, like in an ivar, you can also just call autorelease right after alloc'ing, or use the class's autoreleased creators if available (e.g. [NSString stringWithFormat:] instead of [[NSString alloc] initWithFormat:]).
Selecting Build > Build and Analyze may also reveal such issues.

UserDefaults/KeyedArchiver Frustrations

I'm working on a homework app that uses custom Assignment objects for each assignment. I am trying to store an NSMutableArray (casted to an NSArray via initWithArray:) in standardUserDefaults but I'm having trouble with saving and reloading the array.
I have a table view from which you can choose to add a new assignment (which loads NewAssignmentViewController). When you save the assignment, it is pushed back to an array in AssigmentsViewController. And then you call it every time you load the UITableView which shows the assignments.
Here is the relating code:
-(void)saveToUserDefaults:(NSArray*)myArray{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:myArray] forKey:#"Assignments"];
[standardUserDefaults synchronize];
}
}
-(void)retrieveFromUserDefaults{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"Assignments"];
if (dataRepresentingSavedArray != nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if ([oldSavedArray count] != 0) {
[assignments setArray:[[NSMutableArray alloc] initWithArray:oldSavedArray]];
}
else {
assignments = [[NSMutableArray alloc] initWithCapacity:100];
}
}
}
-(void)backButtonPressed {
[self saveToUserDefaults:[[NSArray alloc] initWithArray:assignments]];
[self.navigationController popViewControllerAnimated:YES];
}
Please help. It does not load the array but does not give any error. Any tips about UserDefault or KeyedArchiver in general would be greatly appreciated.
Couple of things here:
If I understand you correctly, you're trying store an array whose contents are the assignment objects.
If you want to serialize these objects for storage into NSUserDefaults, the Assignment objects themselves need to conform the NSCoding protocol by overriding these methods:
- (void)encodeWithCoder:(NSCoder *)encoder;
- (id)initWithCoder:(NSCoder *)decoder;
Since you didn't post the code for your Assignment objects, dunno if you did this properly or at all. If you have you should be able to encode the object. See the Archives and Serializations Programming Guide for more.
As for NSUserDefaults, by my read, you're basically trying to store your application's object model there. Not the best idea. NSUserDefaults is best suited for use with light-weight persistent data: basic preferences, strings, scraps of universal data.
What I would do is write out your archived data to a file and load it when your view loads.
Here's some code from Beginning iPhone Development on that subject:
Creating an archive from an object or objects that conforms to NSCoding is relatively easy. First, we create an instance of NSMutableData to hold the encoded data and then create an NSKeyedArchiver instance to archive objects into that NSMutableData instance:
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
After creating both of those, we then use key-value coding to archive any objects we wish to include in the archive, like this:
[archiver encodeObject:myObject forKey:#”keyValueString”];
Once we’ve encoded all the objects we want to include, we just tell the archiver we’re done, write the NSMutableData instance to the file system, and do memory cleanup on our objects.
[archiver finishEncoding]; BOOL success = [data writeToFile:#”/path/to/archive” atomically:YES];
[archiver release];
[data release];
To reconstitute objects from the archive, we go through a similar process. We create an NSData instance from the archive file and create an NSKeyedUnarchiver to decode the data:
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
After that, we read our objects from the unarchiver using the same key that we used to archive the object:
self.object = [unarchiver decodeObjectForKey:#”keyValueString”];
You'd also need to get your application's documents directory to save and load the files.
It's a wildly useful book, full of drop in code snippets. The chapter on persistence might be helpful for you. You might be much happier using Core Data for this task, come to think of it.
I'm not sure if this will fix your problem, but you don't have to pull the array out of Defaults as NSData. Check the NSUserDefaults reference and you'll see that Arrays are valid default objects.