iPad/iPhone 2D array serialization - ipad

I have a 2D-array that defines points in one or more paths:
Path#1 = (1,1) (3,3) (6,6)
Path#2 = (5,3) (15,5) (16,46)
Here is my code
NSArray path1 = make array of CGPoints
NSArray path2 = make array of CGPoints
NSMutableArray paths = [[NSMutableArray alloc] init];
[paths addObject:path1];
[paths addObject:path2];
Question: How do I serialize/deserialize this object?

As long as all of the objects in your arrays implement the NSCoding protocol, which most default classes do, you should be able to serialize the entire array structure using an NSKeyedArchiver.
If archiving/unarchiving directly to/from a file, it should look something like this:
NSString *filename = #"[filename]";
[NSKeyedArchiver archiveRootObject:paths toFile:filename];
paths = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
If you just want raw data, maybe to shove in NSUserDefaults or to transmit over the network, it should look something like this:
NSData *binaryData = [NSKeyedArchiver archivedDataWithRootObject:paths];
paths = [NSKeyedUnarchiver unarchiveObjectWithData:binaryData];
If you have custom classes in those arrays, you may have to implement NSCoder yourself, which is tedious, but not difficult.
Also, you may want to be careful with mutable arrays and dictionaries. I seem to remember something about mutable objects becoming immutable when they're archived, so you might want to check before you just start adding objects.
Further reading:
NSCoder Class Reference
Archives and Serializations Programming Guide

Related

Are references maintained when archiving and unarchiving using NSKeyedArchiver?

Suppose I do the following:
CarObject *car1 = [CarObject new];
CarObject *car2 = [CarObject new];
NSArray *carObjectsList1 = #[car1, car2];
NSArray *carObjectsList2 = #[car1, car2];
Also suppose that the car objects implement the NSCoding protocol. Let's say I archive and
unarchive:
//Archive
[NSKeyedArchiver archiveRootObject:carObjectsList1 toFile:#"list1.dat"];
[NSKeyedArchiver archiveRootObject:carObjectsList2 toFile:#"list2.dat"];
//Unarchive
NSArray *unarchivedList1 = [NSKeyedUnarchiver unarchiveObjectWithFile:#"list1.dat"];
NSArray *unarchivedList2 = [NSKeyedUnarchiver unarchiveObjectWithFile:#"list2.dat"];
Then suppose I do this:
CarObject *car1 = unarchivedList1 objectAtIndex:0];
car1.isTireFlat = true;
Would doing this change the property of "Car1" in both arrays? Meaning, would the first car object of both arrays have a flat tire? Are the reference links still preserved? Does anyone know why the answer is as such (as in, how are they preserved/why are they not preserved?)
Edit: I've edited my question to provide a more correct example.
If you archive two arrays that share the same object, you archive the that object twice. Then, when you unarchive those two arrays, you independently unarchive those arrays as if they were different objects. References aren't maintained upon unarchiving since each unarchive creates new object instances by calling initWithCoder: in the NSCoding protocol.
Yes. NSKeyedArchiver can even cope with reference cycles.

Which is the best way to save data (numbers) in a plist file

Initially I had an object made of three properties (numbers 0 to 12). NSCoder and related issues made me avoid using an object and now I store three NSNumbers directly instead. I save a NSMutableArray with the three values in this way
NSMutableArray *data=[[NSMutableArray alloc] initWithObjects: cardSign, cardNumber, cardColor, nil];
I save and check if data are saved
NSLog(#"wrote %hhd", [data writeToFile:path atomically:YES]);
I try to retrieve the data:
NSArray *dataRead = [[NSArray alloc] initWithContentsOfFile:path];
if (dataRead)
{
cardSign = [dataRead objectAtIndex:0];
cardNumber = [dataRead objectAtIndex:1];
cardColor = [dataRead objectAtIndex:2];
}
Before saving the variable values are correct.
When I try to retrieve the values I get all 0 or (null).
Which is the best way to store three numbers in a plist file and how do I retrieve it?
Most likely your objects "cardSign", "cardNumber", and "cardColor", are not actual objects. When storing array contents or NSArray contents to a file all objects must be Apple recognized objects, NSString, NSNumber, etc...
If for instance cardNumber is defined as follows:
int cardNumber;
Then when "data" is initialized if should be something like (pay only attention to cardNumber)
NSMutableArray *data=[[NSMutableArray alloc] initWithObjects: cardSign, [NSNumber numberWithInt:cardNumber], cardColor, nil];
This line is taken directly from discussion section of the Apple API description of the "initWithContentsOfFile" call.
"The array representation in the file identified by aPath must contain only property list objects (NSString, NSData, NSArray, or NSDictionary objects). The objects contained by this array are immutable, even if the array is mutable.

How to read a plist and create different arrays from its content in Xcode

I've got a .plist like this:
Click image for full resolution
How can I create different arrays for each subpart of items. like NSArray foodName with contents of item 0's item 1's to item n's foodName
There are lots of items.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
self.pFile = [[paths objectAtIndex:0]
stringByAppendingPathComponent:#"recipes.plist"];
self.plist = [[NSMutableDictionary alloc] initWithContentsOfFile:pFile];
if (!plist) {
self.plist = [NSMutableDictionary new];
[plist writeToFile:pFile atomically:YES];
}
An NSDictionary is probably what you want. Then do a 'for' on it for each recipe / recipeDetail / wherever you're at in the structure?
You don't need to create "different arrays".
A plist is a textual (or binary) representation of a collection of objects. The valid object kinds come from a small set which includes NSArray, NSDictionary, NSNumber & NSString. The collection is rooted either in a dictionary or an array, each element of which can be any of the valid object kinds, including further dictionaries and arrays.
When you read the plist in your application the collection of objects is re-created. So if there are nested arrays or dictionaries they are recreated. To access them you just need to use the appropriate sequence of indices (arrays) and/or keys (dictionaries) which specify the element you need. You can store the returned object reference into a variable.
So for example if your plist is a dictionary keyed by vegetable names, each element of which is an array, and the third element of that array is another array of observed weights of that vegetable then you can access that array as follows (code just typed into answer, expect errors):
NSDictionary *vegetableInfo = [NSDictionary dictionaryWithContentsofURL:urlOfVegtableInfoPlist"];
NSArray *carrrotObservedWeights = vegetableInfo[#"Carrot"][3];
You now have a reference to the required array stored in carrrotObservedWeights.
If you are concerned over memory management, first use ARC. Second if you just want the extracted arrays and not the whole plist to be kept you just need to drop the reference to the plist after you've stored strong references to the contained arrays and ARC will clean up for you.
HTH
Addendum - After question clarified
First your plist is a dictionary of dictionaries where the containing dictionary has the keys Item 1, Item 2, etc. These keys carry no information and you would be better off making your plist an array of dictionaries.
Next we assume you have read in your plist using one of the standard methods and have a reference to it in sample - which is either an NSDictionary * if your plist is as shown, or an NSArray * if you modify the plist as suggested.
How many ways to do this? Many, here are three.
Method 1 - Simple Iteration
The straightforward way to obtain your arrays is simple iteration - iterate over each item and build your arrays. Here is a code fragment for two of the fields assuming the original dictionary of dictionaries:
NSMutableArray *foodNames = [NSMutableArray new];
NSMutableArray *hardnesses = [NSMutableArray new];
for (NSString *itemKey in sample) // for-in on a dictionary returns the keys
{
NSDictionary *item = sample[itemKey]; // use the key to obtain the contained dictionary
[foodNames addObject:item[#"foodName"]]; // extract each item
[hardnesses addObject:item[#"hardness"]];
}
The above fragment if sample is an array is similar.
Method 2 - Keypaths
If you do switch your plist to an array you can use a keypath to obtain all the values in one go. The method valueforkeypath: creates an array from an array by extracting the keypath - a list of keys separated by dots which allows for dictionaries within dictionaries. In your case the keypath has just one item:
NSMutableArray *foodNames = [sample valueForKeyPath:#"foodName"];
NSMutableArray *hardnesses = [sample valueForKeyPath:#"hardness"];
This will not work for your plist as shown with a top-level dictionary as they keypath is different each time - Item 1.foodName, Item 2.foodName, etc. - and wildcards (e.g. *.foodName) are not supported. A good reason to change your plist to have a top-level array!
Method 3 - Encapsulated Iteration
This is just a variation of method 1, but shows how to use the supplied block enumeration methods on NSDictionary. Rather than write a for loop yourself you can pass the body of the loop as a block to enumerateKeysAndObjectsUsingBlock: and it will perform the iteration:
NSMutableArray *foodNames = [NSMutableArray new];
NSMutableArray *hardnesses = [NSMutableArray new];
[sample enumerateKeysAndObjectsUsingBlock:^(id key, id item, BOOL *stop)
{
[foodNames addObject:item[#"foodName"]];
[hardnesses addObject:item[#"hardness"]];
}];
I'd make the root of the plist an array, but still have each item as a dictionary, so i could do something like this:
//ASSUMING YOUR PLIST IS IN RESOURCES FOLDER
NSString *filepath = [[NSBundle mainBundle] pathForResource:#"YOUR_PLIST_FILE_NAME" ofType:#"plist"];
NSArray *foodArray = [NSArray arrayWithContentsOfFile:filepath];
NSMutableArray *foodNames = [[NSMutable Array] init];
NSInteger i = 0;
for (Item *item in foodArray) {
NSDictionary *itemDict = [foodArray objectAtIndex:i];
NSString *foodName = [itemDict objectForKey:#"foodName"];
[foodNames addObject:foodName];
i++;
}
Hope this helps you figure something out!

Keep NSArray original order in NSCountedSet

I have an NSArray and I need to get data from two keys and put together in a NSMutableDictionary. One key has stringvalues and the other NSNumbervalues. When I try to create NSCountedSetwithout adding the keys I want to use to separate arrays, it doesn't work, because the objects are not identical, basically, I need to check if objectId is identical, don't matter if the other keys are different.
Here is the initial code:
for (PFObject *objeto in objects) {
PFObject *exercicio = objeto[#"exercicio"];
NSString *string = exercicio.objectId;
NSNumber *nota = objeto[#"nota"];
[exercicios addObject:string];
[notas addObject:nota];
So I create two NSMutableArraysand store the values I need. When I logthe arrays after this, they are perfectly ordered, meaning the NSStringis in the same indexof the NSNumberit belongs to in the other array. So far, so good.
Now, when I create the NSCountedSetwith the strings, it changes the order.
NSCountedSet *countedExercicios = [[NSCountedSet alloc] initWithArray:exercicios];.
My goal is to sum the NSNumbers pertaining to an specific object, therefore, when the order changes, I lose the connection between the two arrays.
I'm not sure what I could do to solve this problem, or even if there's a different approach to achieve the result I need.
You can create NSDictionary and add it to array. You will have just one array and you won't lose the connection, you can use objectId as a key and NSNumber as a value:
for (PFObject *objeto in objects) {
PFObject *exercicio = objeto[#"exercicio"];
NSString *string = exercicio.objectId;
NSNumber *nota = objeto[#"nota"];
NSDictionary *dict = #{string: nota};
[newArray addObject: dict];
}
When you need get all key (objectId) you can use NSPredictate.
Hope this help

Reading a .csv into NSObjects and then sorting them by different criterias

This is more a open question than an error-related question, so if you don't like to answer these kinds of questions, please don't flame.
I have a huge (!) list of ships in a .csv file, separated by ,
The matrix is organised like this:
repeated with different data about 500 times.
Now, I want this to be read into objects, which can be used further to populate a UITableView
Currently, I hard-code data into the object files, like this
arrayWithObjectsForTableView = [[NSMutableArray alloc] init];
if ([boatsFromOwner isEqualToString:#"Owner1"]) {
cargoShips* ship = [[cargoShips alloc]init];
ship.name = #"name1";
ship.size = 1000;
ship.owner = #"Owner1";
[self.boatsForOwner addObject:ship];
ship = [[cargoShips alloc]init];
ship.name = #"Name 2";
ship.size = 2000;
ship.owner = #"Owner2";
And so on and on with if-else's. This is a bad method, as
1) Its boring and takes a long time
2) It takes even more time if I want to update the information.
So, I figured it would be smarter to read programmatically from the matrix instead of doing it myself. Yeah, captain obvious came for a visit to my brain.
So, to the question!
How can I read the .csv file that looks like this:
add the ships of, say, owner, to a NSMutableArray, in the shape of objects. (So they can be used to feed my UITableView with ships.
I would also like to have the option to sort by different stuff, like Country of build, Operator etc. How can I make code that feeds relevant ships read from the .csv into objects?
I don't know much programming, so in-depth answers would be very appreciated.
The depth of your processing will determine what sort of data structure is required for this task. This is the method I would use:
1: Read the .csv file into one giant NSString object:
NSString *file = [[NSString alloc] initWithContentsOfFile:yourCSVHere];
2: Get the individual lines:
NSArray *allLines = [file componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
3: For each line, get the individual components:
for (NSString* line in allLines) {
NSArray *elements = [line componentsSeparatedByString:#","];
// Elements now contains all the data from 1 csv line
// Use data from line (see step 4)
}
4: This is where it's up to you. My first thought would be to create a class to store all your data. For example:
#interface Record : NSObject
//...
#property (nonatomic, copy) NSString *name
#property (nonatomic, copy) NSString *owner
// ... etc
#end
4a: Then, back in step 3 create a Record object for each line and then put all the Record objects into a separate NSArray (something with larger scope!).
5: Use your NSArray that contains all your Record objects as the data source for your UITableView.
The implementation of Steps 4 and 5 are up to you. That's probably how I would do it though for a medium sized .csv file.
EDIT: Here's how to generate the Records.
//
NSMutableArray *someArrayWithLargerScope = [[NSMutableArray alloc] init];
//
NSString *file = [[NSString alloc] initWithContentsOfFile:yourCSVHere];
NSArray *allLines = [file componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet];
for (NSString* line in allLines) {
NSArray *elements = [line componentsSeparatedByString#","];
Record *rec = [[Record alloc] init];
rec.name = [elements objectAtIndex:0];
rec.owner = [elements objectAtIndex:1];
// And so on for each value in the line.
// Note your indexes (0, 1, ...) will be determined by the
// order of the values in the .csv file.
// ...
// You'll need one `Record` property for each value.
// Store the result somewhere
[someArrayWithLargerScope addObject:rec];
}
In terms of the CSV parsing, assuming you can spend the memory it's probably easiest to read in the whole thing to an NSString, split that on newlines and then split each line on commas, essentially as PLPiper suggests.
At that point I'd take a detour into key-value coding. Give your columns in the CSV file exactly the same name as the properties on your runtime object. Then you can just write something like:
// this method will create an object of the given type then push the values
// from valueRow to the properties named by headingRow. So, ordinarily,
// headingRow will be the first row in your CSV, valueRow will be any other
- (id)populatedObjectOfType:(Class)type withHeadingRow:(NSArray *)headingRow valueRow:(NSArray *)valueRow
{
// we need the count of fields named in the heading row to
// match the count of fields given in this value row
if([headingRow count] != [valueRow count]) return nil;
// autorelease if you're not using ARC
id <NSObject> newInstance = [[type alloc] init];
// we need to enumerate two things simultaneously, so
// we can fast enumerate one but not the other. We'll
// use good old NSEnumerator for the other
NSEnumerator *valueEnumerator = [valueRow objectEnumerator];
for(NSString *propertyName in headingRow)
{
[newInstance setValue:[valueEnumerator nextObject] forKey:propertyName];
}
return newInstance;
}
... elsewhere ....
CargoShip *newShip = [self populateObjectOfType:[CargoShip class] withHeadingRow:[csvFile objectAtIndex:0] valueFor:[csvFile objectAtIndex:1]];
The main caveat is that the built-in mechanisms will convert between scalars and objects but not between objects of different types. So if you had all NSString and C integer types (short, int, NSUInteger, etc) you'd be fine, but if you had some NSStrings and, say, some NSNumbers then you would end up with strings stored in the number slots. It looks like you're using C integer types (as is quite normal) so you should be fine.
In terms of filtering, you can use NSPredicates. For example, suppose you had an array of CargoShips and wanted every one with a size of at least 500:
NSArray *bigShips = [allShips filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"size > 500"]];
Similarly, for sorting you can throw some NSSortDescriptors at the problem. E.g.
NSArray *shipsSortedBySize = [allShips sortedArrayUsingDescriptors:
#[[NSSortDescriptor sortDescriptorWithKey:#"size" ascending:YES]]];
you can use this link,
https://github.com/davedelong/CHCSVParser
It is the .csv parser and CHCSVParser can be configured to parse other "character-seperated" file formats, such as "TSV" (tab-seperated) or comma seperated.

Resources