What's the best way to store and retrieve multi-dimensional NSMutableArrays? - iphone

I'm storing a bunch of data in a .plist file (in the application documents folder), and it's structured like this:
Dictionary {
"description" = "String Value",
"sections" = Array (
Array (
Number,
...
Number
),
Array (
Number,
...
Number
)
),
"items" = Array (
Array (
Number,
...
Number
),
Array (
Number,
...
Number
)
)
}
If I just retrieve it with
NSMutableDictionary *d = [[NSMutableDictionary alloc] initWithContentsOfFile:plistFile]
I won't be able to replace the number objects, correct?
So I'm recursing through the data right now and forming a mutable version of the whole thing, and it worked in one instance, but now it's telling me mutating method sent to immutable object when the whole thing is mutable.
Is there an easier/better way to do this? If it makes a difference, my data is just integers and booleans.

Instead of writing all that custom class junk, you should use NSPropertyListSerialization. Specifically, see the propertyListWithData:options:format:error: method. Example usage:
NSMutableDictionary *d = [NSPropertyListSerialization propertyListWithData:[NSData dataWithContentsOfFile:#"path/to/file"]
options:NSPropertyListMutableContainers
format:NULL
error:NULL];
This will make all the containers mutable, but keep the leaf nodes (e.g. NSStrings) immutable. There's also an option to make the leaves mutable too.

I usually find it easier to create one or more custom classes to handle loading and saving. This lets you convert the arrays to mutableArrays explicitly:
MyThing.h
#interface MyThing : NSObject
{
NSString * description;
NSMutableArray * sections;
NSMutableArray * items;
}
#property (copy) NSString * description;
#property (readonly) NSMutableArray * sections;
#property (readonly) NSMutableArray * items;
- (void)loadFromFile:(NSString *)path;
- (void)saveToFile:(NSString *)path;
#end
MyThing.m
#implementation MyThing
#synthesize description;
#synthesize sections
#synthesize items;
- (id)init {
if ((self = [super init]) == nil) { return nil; }
sections = [[NSMutableArray alloc] initWithCapacity:0];
items = [[NSMutableArray alloc] initWithCapacity:0];
return self;
}
- (void)dealloc {
[items release];
[sections release];
}
- (void)loadFromFile:(NSString *)path {
NSDictionary * dict = [NSDictionary dictionaryWithContentsOfFile:path];
[self setDescription:[dict objectForKey:#"description"]];
[sections removeAllObjects];
[sections addObjectsFromArray:[dict objectForKey:#"sections"]];
[items removeAllObjects];
[items addObjectsFromArray:[dict objectForKey:#"items"]];
}
- (void)saveToFile:(NSString *)path {
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
description, #"description",
sections, #"sections",
items, #"items",
nil];
[dict writeToFile:path atomically:YES];
}
#end;
With that done, you can encapsulate all of the packaging and unpackaging code in your loadFromFile and saveToFile methods. The major benefit of this approach is that your main program gets a lot simpler, and it allows you to access the elements of your data structure as properties:
MyThing * thing = [[MyThing alloc] init];
[thing loadFromFile:#"..."];
...
thing.description = #"new description";
[thing.sections addObject:someObject];
[thing.items removeObjectAtIndex:4];
...
[thing saveToFile:#"..."];
[thing release];

What you want is a deep mutable copy. Cocoa doesn't include a way to do it. A few people have written such deep-copy implementations before (example).
However, Core Foundation includes the CFPropertyList API, which does have support both for creating deep mutable copies of property list objects as well as reading in property lists from disk as mutable datatypes. (And, of course, Core Foundation's property list types are toll-free bridged with Cocoa's, meaning you don't have to convert between them — an NSArray is a CFArray and vice-versa.)

Related

Saving NSMutableArray to the iPhone Documents Directory

I am trying to create a mutable array with objects of a custom class called Dog and save it to a file in the iPhone documents directory to be later read out of the file and back into my application. I am trying to use NSArray's writeToFile:atomically: method to accomplish this, but when I test the results of this method, it always returns a value of NO, and the file is not created, and the array is not stored. I have a few questions about this. What file format should I save my array to? What does it mean to atomically write an array to a file? How do I read out the contents of the file once the array is stored there And most importantly, why is my array not being stored into a file at the specified path? Thank you in advance and here is the code that I am using within my app's viewDidLoad method:
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
dogFilePath = [documentsDir stringByAppendingPathComponent:#"arrayDogsFile.plist"];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSLog(#"%#",dogFilePath);
Dog *dog1 = [[Dog alloc] init];
dog1.name = #"Dog1";
Dog *dog2 = [[Dog alloc] init];
dog2.name = #"Dog2";
Dog *dog3 = [[Dog alloc] init];
dog3.name = #"Dog3";
NSMutableArray *arrayDogs = [NSMutableArray array];
[arrayDogs addObject: dog1];
[arrayDogs addObject: dog2];
[arrayDogs addObject: dog3];
//Sorts the array in alphabetical order according to name – compareDogNames: is defined in the Dog class
arrayDogs = (NSMutableArray *)[arrayDogs sortedArrayUsingSelector:#selector(compareDogNames:)];
if ([arrayDogs writeToFile:dogFilePath atomically:YES])
NSLog(#"Data writing successful");
else
NSLog(#"Data writing unsuccessful");
You can not save your array of objects because objects are not NSString, NSData, NSArray, or NSDictionary.You could rather use NSKeyArchiver and NSKeyUnArchiver
For example:
#import "Foundation/Foundation.h"
#interface Dog : NSObject {**NSCoding**}//your class must conform to NSCoding Protocol
#property (retain) NSString *Name;
#end
The implementation needs some additional code. We need to implement the NSCoding protocol, which means
two additional methods. (initWithCoder: and encodeWithCoder:)
#import "Dog.h"
#implementation Dog
#synthesize Name;
-(id)initWithCoder:(NSCoder*)decoder{
if ((self = [super init])) {
Name = [decoder decodeObjectForKey:#"Name"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder*)encoder{
[encoder encodeObject:Name forKey:#"Name"];
}
Once we implement the protocol, saving will look like this:
// Save method
// We initialise our object and set the values
Dog *dog1 = [[Dog alloc] init];
dog1.Name= #"Dog1";
Dog *dog2 = [[Dog alloc] init];
dog2.Name= #"Dog2";
Dog *dog3 = [[Dog alloc] init];
dog3.Name= #"Dog3";
NSMutableArray *arrayDogs = [NSMutableArray array];
[arrayDogs addObject: dog1];
[arrayDogs addObject: dog2];
[arrayDogs addObject: dog3];
//Sorts the array in alphabetical order according to name – compareDogNames: is defined in the Dog class
arrayDogs = (NSMutableArray *)[arrayDogs sortedArrayUsingSelector:#selector(compareDogNames:)];
// Store the array
[NSKeyedArchiver archiveRootObject:arrayDogs toFile:dogFilePath];
//load the array*
NSMutableArray* retreivedADogObjs = [NSKeyedUnarchiver unarchiveObjectWithFile:dogFilePath];
#end
Hope it will help you
Happy to help.*
Look at NSKeyedArchiver & NSKeyedUnarchiver Specifically you want + archiveRootObject:toFile: to save the file and + unarchiveObjectWithFile: to extract it again.
You will need to implement the NSCoding protocol in your Dog class as well to make this work. You just need to use something like - encodeObject: forKey: and – decodeObjectForKey: for each of your properties. The docs for NSCoder will show you which method to use for which kinds of property types (for example with BOOLs you use - encodeBool: forKey:).
Industrial answer:
There's a whole subject for this in the SDK called "Archiving and Serialization".
If you don't have time to learn, but your dog does:
Teach your Dog two new tricks: 1. How to render myself as a dictionary of strings and ints and so on. 2. How to init myself from that same kind of dictionary. This is basically a ghetto version of the industrial answer.
// Dog.m
- (NSDictionary *)asDictionary {
NSMutableDictionary *answer = [NSMutableDictionary dictionary];
[answer setValue:self.name forKey:#"name"];
[answer setValue:self.numberOfBones forKey:#"numberOfBones"];
return [NSDictionary dictionaryWithDictionary:answer];
}
- (id)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
self.name = [dictionary valueForKey:#"name"];
self.numberOfBones = [dictionary valueForKey:#"numberOfBones"];
}
return self;
}
When writing:
[arrayDogs addObject: [dog1 asDictionary]];

objective c how to create dictionaries dynamically and refer to them

i need to create and destroy dynamically dictionaries, or arrays,
and have them as instance variables,
so for example, [pseudocode]
*.h
nsmutableDictionary myDictn???
nsstring arrayn ???
how to create an instance dictionarie, and property, that dinamically get created and destroyed?, and how to refer to it?
*.m
n = 0
create container {
myDictn alloc init
n+1
}
other {
myDictn addobject#"data" forKey"myKey"
}
destroy container {
myDictn release
n-1
}
So what intend to show is that i would like to have myDict1, myDict2...
if created,
or destroy them if needed
thanks a lot!
To create dictionaries dynamically & add entries to them you could do this -
NSMutableDictionary *dictResult = [[[NSMutableDictionary alloc] init] retain];
[dictResult setValue:result forKey:#"key"];
Here result can be anything. NSString or NSArray etc. Also using retain retains this object & causes a memory leak if not explicitly released. Instead try to do autorelease that way ios takes care of releasing the object when its no longer referred to. You do that like so -
NSMutableDictionary *dictResult = [[[NSMutableDictionary alloc] init] autorelease];
This is all you need to create dictionaries dynamically.
I think what you're asking for is how to have multiple mutable dictionaries dynamically created. You haven't said where the numbering scheme is coming from, so you may need to modify this solution for your purposes.
What you want is an array or dictionary of dictionaries.
Make one NSMutableDictionary called something like dictionaryContainer. Then, when you want to create dictionary number 7, do
NSMutableDictionary *aDictionary = [NSMutableDictionary new];
[dictionaryContainer setObject:aDictionary forKey:[NSNumber numberWithInt:7]];
To recall that dictionary, do
NSMutableDictionary *theSameDictionary = [dictionaryContainer objectForKey:[NSNumber numberWithInt:7]];
You don't have to hard code the 7, you can get it from anywhere and pass it in as an integer variable.
If I got your question correctly, this is pretty easy
#interface MyClass {
NSMutableDictionary *dict;
NSMutableArray *arr;
}
#property (nonatomic, retain) NSMutableDictionary *dict;
#property (nonatomic, retain) NSMutableArray *arr;
#end
Implementation file
#import "MyClass.h"
#implementation MyClass
#synthesize dict;
#synthesize arr;
- (id) init {
self = [super init];
if (self) {
dict = [[NSMutableDictionary alloc] init];
arr = [[NSMutableArray alloc] init];
}
return self;
}
- (void) dealloc {
[dict release];
[arr release];
[super dealloc];
}
- (void) otherStuff {
[dict setObject: #"value" forKey: #"key"];
[arr addObject: #"item"];
}
#end
usage from another class:
...
MyClass *instance = [MyClass new];
[instance.dict setObject: #"value" forKey: #"key"];
NSLog(#"Array items: %#", instance.arr);
[instance release];
...

Putting single attribute from group of entity objects into an array

If I have an NSArray of custom objects (in this case Core Data objects), how would I put all the items of a particular attribute in another NSArray. Is there a way I can use blocks?
For instance, if the class is People, and one of the attributes is age, and there are five objects in the array of people, then the final NSArray would just show the ages:
{ 12, 45, 23, 43, 32 }
Order is not important.
EDIT I have added a blocks based implementation too alongwith the selector based implementation.
What you are looking for is something equivalent to the "map" function from the functional world (something which, unfortunately, is not supported by Cocoa out of the box):
#interface NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector;
- (NSArray *)mapWithBlock:(id (^)(id obj))block;
#end
And the implementation:
#implementation NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:[object performSelector:selector]];
}
return [result autorelease];
}
- (NSArray *)mapWithBlock:(id (^)(id obj))block {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:block(object)];
}
return [result autorelease];
}
#end
Now, when you need to "map" from people to their ages, you can just do this:
ages = [people mapWithSelector:#selector(age)];
OR
ages = [people mapWithBlock:^(Person *p) { return [p age]; }];
The result, ages, will be a new NSArray containing just the ages of the people. In general, this will work for any sort of simple mapping operations that you might need.
One caveat: Since it returns an NSArray, the elements inside ages should be NSNumber, not a plain old integer. So for this to work, your -age selector should return an NSNumber, not an int or NSInteger.
Assuming that each object has a method called "age" that returns an NSNumber *, you should be able to do something like the following:
-(NSArray *)createAgeArray:(NSArray *)peopleArray {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[peopleArray count]];
[peopleArray enumerateObjectsUsingBlock:^(id person, NSUInteger i, BOOL *stop) {
[result addObject:[person age]];
}];
return [result autorelease];
}

Sorting NSDictionary with multiple constraints

I have a NSDictionary collection whose key is a unique id and value is an array with two different objects (FruitClass, ProductClass) and I would like to group the collection such that it's sorted first by ProductClass.productName and then by FruitClass.itemName.
So the final list would look something like:
{apple, butter}
{apple, pie}
{banana, daiquiri}
{banana, smoothie}
{melon, zinger}
where the first item is a FruitClass instance item and second is a ProductClass instance item.
What's the best way to go about doing this? Most of the examples I've come across are done on one key. How do you do it with an NSDictionary that has 2 different object types?
Looking at NSDictionary's's keysSortedByValueUsingSelector,
- (NSArray *)keysSortedByValueUsingSelector:(SEL)comparator
I get the impression that you would create the 'compare' method on the class type of the value object. So for multiple field sort, would I have to resort to creating a new object type, 'CombinedClass' which contains FruitClass & ProductClass and implement a 'compare' to make this happen?
FruitClass:
{
NSString *itemName;
}
#end
#interface ProductClass
{
NSString *productName;
}
#end
If there is a data structure that consists of only one fruit and only one product then an array is not really a good option. You can use another class and provide a compare: comparator:
#interface ComboClass : NSObject
{
FruitClass *fruit;
ProductClass *product;
}
#property(nonatomic,retain) FruitClass *fruit;
#property(nonatomic,retain) ProductClass *product;
- initWithFruit:(FruitClass *)f andProduct:(ProductClass *) p;
#end
#implementation ComboClass
#synthesize fruit;
#synthesize product;
- (void) dealloc
{
[fruit release];
[product release];
[super dealloc];
}
- initWithFruit:(FruitClass *)f andProduct:(ProductClass *) p
{
self = [super init];
if (!self) return nil;
self.fruit = f; // some recommend against accessor usage in -init methods
self.product = p;
return self;
}
- (NSComparisonResult) compare:(id) another
{
NSComparisonResult result = [self.fruit.itemName compare:another.fruit.itemName];
if (result == NSOrderedSame)
return [self.product.productName compare:another.product.productName];
else
return result;
}
#end
Alternatively, you might be able to use an NSDictionary with product and fruit key-value pairs, (so you'll end up with dictionaries inside a dictionary). The NSSortDescriptor class can be used to sort arrays using values of key-paths, so it might be another option to explore.
You could add a category to NSArray that would do the comparison and you wouldn't have to create another class.
#interface NSArray ( MySortCategory )
- (NSComparisonResult)customCompareToArray:(NSArray *)arrayToCompare;
#end
The implementation should be pretty straightforward based on your description.
Edit I got a little miffed that this was marked down with no comment, so I did a full implementation to make sure it would work. This is a little different than your sample, but the same idea.
FruitsAndProducts.h
#interface Fruit : NSObject
{
NSString *itemName;
}
#property(nonatomic, copy)NSString *itemName;
#end
#interface Product : NSObject
{
NSString *productName;
}
#property(nonatomic, copy)NSString *productName;
#end
FruitsAndProducts.m
#import "FruitsAndProducts.h"
#implementation Fruit
#synthesize itemName;
#end
#implementation Product
#synthesize productName;
#end
NSArray+MyCustomSort.h
#interface NSArray (MyCustomSort)
- (NSComparisonResult)customCompareToArray:(NSArray *)arrayToCompare;
#end
NSArray+MyCustomSort.m
#import "NSArray+MyCustomSort.h"
#import "FruitsAndProducts.h"
#implementation NSArray (MyCustomSort)
- (NSComparisonResult)customCompareToArray:(NSArray *)arrayToCompare
{
// This sorts by product first, then fruit.
Product *myProduct = [self objectAtIndex:0];
Product *productToCompare = [arrayToCompare objectAtIndex:0];
NSComparisonResult result = [myProduct.productName caseInsensitiveCompare:productToCompare.productName];
if (result != NSOrderedSame) {
return result;
}
Fruit *myFruit = [self objectAtIndex:1];
Fruit *fruitToCompare = [arrayToCompare objectAtIndex:1];
return [myFruit.itemName caseInsensitiveCompare:fruitToCompare.itemName];
}
#end
Here it is in action
// Create some fruit.
Fruit *apple = [[[Fruit alloc] init] autorelease];
apple.itemName = #"apple";
Fruit *banana = [[[Fruit alloc] init] autorelease];
banana.itemName = #"banana";
Fruit *melon = [[[Fruit alloc] init] autorelease];
melon.itemName = #"melon";
// Create some products
Product *butter = [[[Product alloc] init] autorelease];
butter.productName = #"butter";
Product *pie = [[[Product alloc] init] autorelease];
pie.productName = #"pie";
Product *zinger = [[[Product alloc] init] autorelease];
zinger.productName = #"zinger";
// create the dictionary. The array has the product first, then the fruit.
NSDictionary *myDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObjects:zinger, banana, nil], #"zinger banana", [NSArray arrayWithObjects:butter, apple, nil], #"butter apple", [NSArray arrayWithObjects:pie, melon, nil], #"pie melon", nil];
NSArray *sortedKeys = [myDict keysSortedByValueUsingSelector:#selector(customCompareToArray:)];
for (id key in sortedKeys) {
NSLog(#"key: %#", key);
}
Your comparator can work on whatever you throw at it... you can make it treat its two arguments as NSArray objects, if this is what you need. When you put arrays as values into your dictionary, then just use those - no need for another class.
If you want to build a new class anyway (maybe for design reasons) - go for it, but it is not a "must do" here.
Edit: strike out to make clear that only one argument is given - as the other one is the object the selector is called on. Using NSArray will need a class extension, a custom class is much cleaner.

Filling class variables from an NSDictionary in a loop

I would like to fill in the class variables in a loop from an dictionary. What I want to do is having the dictionary key as a class variable and assign the class variable (the dictionary key) the value from dictionary... something like this:
+(void) initWithDictionary:(NSDictionary *)dic {
MyClass *classInstance = [[[self alloc] init] autorelease];
NSArray *allKeys = [dic allKeys];
for(NSUInteger i = 0; i < [allKeys count]; i++)
{
id classVariable = [allKeys objectAtIndex:i];
classInstance.classVariable = [dic objectForKey:[allKeys objectAtIndex:i]];
}
return classInstance;
}
It does not work, because I do not know how to assign the class variable from the string.
Thanks for answer, I am returning a JSON string that gives me an NSDictionary with keys and values. I am trying to fill this values to my class, let's say DetailObject. I want to use later in the project the DetailObject.id, DetailObject.description, etc. I would like to do it in a loop, becouse now I have to write this:
+ (id) initWithDiccionary :(NSDictionary *)dic//;
{
//Instantiating an object of this class... that's okay.
DetailObject *classInstance = [[[self alloc] init] autorelease];
classInstance.id = [dic objectForKey#"id"];
classInstance.desc = [dic objectForKey#"desc"];
etc... etc...
return classInstance;
}
What I want is to parse the dictionary from JSON to my object and respective variables and values that comes from dictionary in a loop, because if the JSON dictionary changes, I just add the new class variable with the same name of the returned dictionary key...
I do not know if I have explained it well...
Your question is very very unclear and I have no idea what you're trying to do or why. But just looking at your code I can tell you already that it's definitely not doing what you want.
//There should be no semicolon after "dic" below.
//Also, you should be returning a MyClass or an id.
- (id) initWithDiccionary :(NSDictionary *)dic//;
{
//Instantiating an object of this class... that's okay.
MyClass *classInstance = [[[self alloc] init] autorelease];
//Getting all the keys from the dictionary, seems fine...
NSArray *allKeys = [dic allKeys];
//Looping through all the keys in the dictionary, seems okay...
for(NSUInteger i = 0; i < [allKeys count]; i++)
{
//Storing the current key.
id classVariable = [allKeys objectAtIndex:i];
//Assigning the class's property "classVariable" to match the current key's value.
//No reason to say "[allKeys objectAtIndex:i]" again, though.
classInstance.classVariable = [dic objectForKey:classVariable];
}
//Returning something when you have no return type above (void) is wrong.
return classInstance;
}
Your code will just assign classInstance.classVariable to be equal to [allKeys objectAtIndex:[allKeys count]-1]. Your loop is pointless.
After I actually annotated your code though I think I have some idea of what you want. Basically you want to assign the variables with names matching the keys in the dictionary the values in the dictionary. i.e. if there is a key called "superKey" then you want to find the variable within classInstance (classInstance.superKey) and assign it the value in the dictionary that matches superKey. That's what you want, right?
Well, the only way I know of to do that is to use a big switch statement or a bunch of if statements. Make some function within MyClass like this:
- (void) assignProperty:(id)property toValue:(id)value
{
if (property == #"superKey")
{
self.superKey = value;
}
else if (property == #"lameKey")
{
self.lameKey = value;
}
//etc.
}
Then you just call [classInstance assignProperty:classVariable toValue:[doc objectForKey:classVariable]] and the job will be done.
But having told you all that...
Why would you ever want to do what you're doing? Want to know a much better way of doing this? Give MyClass its own NSDictionary. Basically all you are doing is defeating the entire purpose of the dictionary. Why? They are incredibly fast to access and can store whatever you want. There is no reason not to use one. So just do this:
- (id) initWithDiccionary :(NSDictionary *)dic
{
MyClass *classInstance = [[[self alloc] init] autorelease];
classInstance.dictionary = dic;
return classInstance;
}
Voila.
Enter Key-Value Coding. The following is an example of how you could achieve your desired outcome:
#interface MyClass : NSObject
#property (nonatomic, retain) NSString *aString;
#property (nonatomic, retain) NSNumber *aNumber;
#property (nonatomic, retain) NSString *yetAnother;
- (id)initWithDictionary:(NSDictionary *)dictionary;
#end
#implementation MyClass
#synthesize aString;
#synthesize aNumber;
#synthesize yetAnother;
- (id)initWithDictionary:(NSDictionary *)dictionary {
if ((self = [super init])) {
[self setValuesForKeysWithDictionary:dictionary];
}
return self;
}
// dealloc is left as an exercise for the reader
#end
You could use this class as follows:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
#"my string", #"aString",
[NSNumber numberWithInt:42], #"aNumber",
#"strings!", #"yetAnother", nil];
MyClass *myClass = [[MyClass alloc] initWithDictionary:dictionary] autorelease];
// yay!
You can thank Objective-C's dynamism for that. :)