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.
Related
I have a NSString in an NSArray and I wanted to order this string/fields based on how important it is. So say the string is B, H, A, Q, Z, L, M, O.
I wanted it to be always sorted as A, Q, Z, B, H, O, L, M. This is a predefined set of rule. How do I do this? Can this be done using NSSortDescriptor?
The short answer: Yes!
And here's how...
Since there are two pieces of information you need to know about your value (the importance and the value itself), you should create an object with these two important pieces of information, then store in an array similar to the way you store your strings. This makes it simple if, say, you want to change the 'importance' some time later with very little effort:
#interface MyObject : NSObject
#property (nonatomic, readwrite) NSInteger sortOrder;
#property (nonatomic, retain) NSString *value;
#end
#implementation MyObject
#synthesize sortOrder;
#synthesize value;
-(NSString *)description
{
//...so I can see the values in console should I NSLog() it
return [NSString stringWithFormat:#"sortOrder=%i, value=%#", self.sortOrder, self.value];
}
-(void)dealloc
{
self.value = nil;
[super dealloc];
}
#end
Add your objects to an array. Then sort:
NSMutableArray *myArrayOfObjects = [NSMutableArray array];
//Add your objects
MyObject *obj = [[[MyObject alloc] init] autorelease];
obj.sortOrder = 1;
obj.value = #"A";
[myArrayOfObjects addObject:obj];
obj = [[[MyObject alloc] init] autorelease];
obj.sortOrder = 2;
obj.value = #"Q";
[myArrayOfObjects addObject:obj];
//Sort the objects according to importance (sortOrder in this case)
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"sortOrder" ascending:YES] autorelease];
NSArray *sortedArray = [myArrayOfObjects sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSLog(sortedArray); //<--See for yourself that they are sorted
NSArray has several sort functions. Three you might consider are:
- (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr
- (NSArray *)sortedArrayUsingSelector:(SEL)comparator
- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context
I think you might find the second, selector-based comparator the easiest to use to get started. See the docs here:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html#//apple_ref/occ/instm/NSArray/sortedArrayUsingSelector:
EDIT:
I think using NSSortDescriptor may be overkill, but here is a good post describing it:
How to sort NSMutableArray using sortedArrayUsingDescriptors?
In my application I have an NSArray which contains some data. I want to take that data and put it into an NSMutableArray called subArrayData. I am able to insert the data from my first array into the mutable array, but when the application runs I am getting:
warning:Incompatible pointer types assigning to 'nsmutablearray *'
from 'nsarray *' please help out.
following is my code:
.h file
#import <UIKit/UIKit.h>
#interface AddNew : UIViewController
{
NSMutableArray *subArrayData;;
}
#property(nonatomic ,retain)NSMutableArray *subArrayData;
.m file
#import "AddNew.h"
#import "DashBoardPage.h"
#import "SubmitYourListing.h"
#implementation AddNew
#synthesize subArrayData;
-(void)accommodationAndTravel
{
subArrayData =[[NSArray alloc] initWithArray:[NSArray arrayWithObjects:#"Select one",#"Accommodation and travel hospitality",#"Apartments and villas",#"Bed and Breakfast",#"Caravan parks and campsites",#"Hospitality",
#"Hotels and Motels",#"Snow and Ski lodges",#"Tourist attractions and tourism information",#"Tours and Holidays",#"Travel agents and Services",nil]];
}
You can convert any NSArray to an NSMutable array by calling its -mutableCopy method:
NSArray *someArray = ...;
NSMutableArray* subArrayData = [someArray mutableCopy];
change the .m file
#import "AddNew.h"
#import "DashBoardPage.h"
#import "SubmitYourListing.h"
#implementation AddNew
#synthesize subArrayData;
-(void)accommodationAndTravel
{
subArrayData =[[NSMutableArray alloc] initWithArray:[NSArray arrayWithObjects:#"Select one",#"Accommodation and travel hospitality",#"Apartments and villas",#"Bed and Breakfast",#"Caravan parks and campsites",#"Hospitality",
#"Hotels and Motels",#"Snow and Ski lodges",#"Tourist attractions and tourism information",#"Tours and Holidays",#"Travel agents and Services",nil]];
}
Just call one of NSMutableArray's initializers, like so:
subArrayData = [[NSMutableArray alloc] initWithObjects:
#"Select one",
#"Accommodation and travel hospitality",
#"Apartments and villas",
#"Bed and Breakfast",
#"Caravan parks and campsites",
#"Hospitality",
#"Hotels and Motels",
#"Snow and Ski lodges",
#"Tourist attractions and tourism information",
#"Tours and Holidays",
#"Travel agents and Services",
nil]];
For the cases when you are dealing with an existing NSArray, you can make a mutableCopy:
- (id)initWithArray:(NSArray *)array
{
self = [super init];
if (nil != self) {
subArrayData = [array mutableCopy];
...
How do I build up a global like integer array?
I tried variations of the following:
#interface
int *iArray; //this space will vary depending upon need in the implementation
#implementation
...
int iArrayInit[4] = {1,2,3,4};
iArray = iArrayInit;
-bottom line: I need to keep index values in array that I can access easily, and use of [NSArray intValue] maybe to slow.
thanks
If it needs to be static you can declare an NSMutableArray as static in the implementation file and expose static methods to access it. When using an NSArray the values need to be of type id which NSNumber can do. Here is an example which currently is not thread safe.
//.h file
#interface Foo : NSObject
{
}
+(NSArray*)iArray;
+(void)addiArrayValue:(NSNumber*)value;
#end
//.m file
#implementation Foo
static NSMutableArray *_iArray;
+(void)initialize
{
if([Foo class] == self)
{
_iArray = [[NSMutableArray alloc] init];
}
}
+(NSArray*)iArray
{
return [[_iArray copy] autorelease];
}
+(void)addiArrayValue:(NSNumber*)value
{
[_iArray addObject:value];
}
#end
//Use
[Foo addiArrayValue:[NSNumber numberWithInt:10]];
[Foo addiArrayValue:[NSNumber numberWithInt:12]];
NSLog(#"%#", [Foo iArray]);
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.)
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. :)