iPhone development question (ObjectiveC).
I'm trying to use TouchJSON library, and having some trouble serialising to JSON. I have ARC switched on so I'm using the ARC branch from github. I'm trying what I imagine to be a fairly a basic nested structure. Three dictionaries inside and array inside a dictionary.
//Make some dictionaries with simple string pairs
NSDictionary *dicA = [NSDictionary dictionaryWithObjectsAndKeys:#"x", #"1", #"y", #"2", nil];
NSDictionary *dicB = [NSDictionary dictionaryWithObjectsAndKeys:#"x", #"1", #"y", #"2", nil];
NSDictionary *dicC = [NSDictionary dictionaryWithObjectsAndKeys:#"x", #"1", #"y", #"2", nil];
//Make an array of dictionary objects
NSArray *saveArray = [NSArray arrayWithObjects:dicA, dicB, dicC, nil];
//Make dictionary which has that array as one of the values
NSDictionary *bigDic = [NSDictionary dictionaryWithObjectsAndKeys:#"arr", saveArray,
#"mmm", #"nnn", nil];
NSData *jsonData = [[CJSONSerializer serializer] serializeObject:saveArray error:NULL];
//This works '[{"1":"x","2":"y"},{"1":"x","2":"y"},{"1":"x","2":"y"}]'
NSData *jsonDataB = [[CJSONSerializer serializer] serializeObject:bigDic error:NULL];
//This fails
When I try to serialize bigDic it bombs out at runtime with the following:
'NSInvalidArgumentException', reason: '-[__NSArrayI UTF8String]:
unrecognized selector sent to instance
Serializing an array on the line above seems to work OK. What's wrong with my bigDic?
After carefully writing out this question I realised where I had gone wrong. Thought I'd post this anyway. Maybe it's a helpful example for others. So the answer is...
I have my dictionaries back to front!
The dictionaryWithObjectsAndKeys method expects the values and keys the other way around so the correct way to build this structure is:
//Make some dictionaries with simple string pairs
NSDictionary *dicA = [NSDictionary dictionaryWithObjectsAndKeys:#"1", #"x", #"2", #"y", nil];
NSDictionary *dicB = [NSDictionary dictionaryWithObjectsAndKeys:#"1", #"x", #"2", #"y", nil];
NSDictionary *dicC = [NSDictionary dictionaryWithObjectsAndKeys:#"1", #"x", #"2", #"y", nil];
//Make an array of dictionary objects
NSArray *saveArray = [NSArray arrayWithObjects:dicA, dicB, dicC, nil];
//Make dictionary which has that array as one of the values
NSDictionary *bigDic = [NSDictionary dictionaryWithObjectsAndKeys:saveArray, #"arr",
#"nnn", #"mmm", nil];
This makes sense when you look at the method name "dictionaryWithObjectsAndKeys", but why it isn't done as "dictionaryWithKeysAndObjects" I have no idea.
Related
Aim: To obtain an NSArray containing unique keys for given NSDictionary(s) using elegant code
Example Code with Current Working Solution:
NSArray *data = [[NSArray alloc] initWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1], #"a", [NSNumber numberWithInt:2], #"b", nil],
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:3], #"b", [NSNumber numberWithInt:4], #"c", nil],
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:5], #"a", [NSNumber numberWithInt:6], #"c", nil],
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:7], #"b", [NSNumber numberWithInt:8], #"a", nil],
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:8], #"c", [NSNumber numberWithInt:9], #"b", nil],
nil];
// create an NSArray of all the dictionary keys within the NSArray *data
NSMutableSet *setKeys = [[NSMutableSet alloc] init];
for (int i=0; i<[data count]; i++) {
[setKeys addObjectsFromArray:[[data objectAtIndex:i] allKeys]];
}
NSArray *arrayKeys = [setKeys allObjects];
NSLog(#"arrayKeys: %#", arrayKeys);
Which returns the desired array of keys:
2012-06-11 16:52:57.351 test.kvc[6497:403] arrayKeys: (
a,
b,
c
)
Question: Is there a more elegant way of approaching this? Surely there must be some KVC approach that can get all the keys without having to iterate through the array? I've been looking at Apple Developer Documentation and can't see a solution. Any ideas? (looking at purely elegance of code rather than performance).
Normally you could use KVC by doing something like this:
NSArray *uniqueKeys = [data valueForKeyPath:#"#distinctUnionOfArrays.allKeys";
However NSDictionary overrides the valueForKey: selector which is used by the KVC internals, so this will not work correctly.
The documentation for NSDictionary's valueForKey: method tells us that:
If key does not start with “#”, invokes objectForKey:. If key does start with “#”, strips the “#” and invokes [super valueForKey:] with the rest of the key.
So we just insert an # before allKeys:
NSArray *uniqueKeys = [data valueForKeyPath:#"#distinctUnionOfArrays.#allKeys"];
And we get what we want:
(lldb) po [data valueForKeyPath:#"#distinctUnionOfArrays.#allKeys"]
(id) $14 = 0x07bb2fc0 <__NSArrayI 0x7bb2fc0>(
c,
a,
b
)
This is less ugly, and possibly marginally faster, I suppose:
NSMutableSet *setKeys = [[NSMutableSet alloc] init];
for (NSDictionary* dict in data) {
for (id key in [dict keyEnumerator]) {
[setKeys addObject:key];
}
}
But you're not doing a particularly common operation, so I wouldn't expect to find some incredibly elegant method. If that's what you want, go learn Haskell.
You could try this:
NSMutableSet *setKeys = [[NSMutableSet alloc] init];
for(NSDictionary *dict in data) {
[setKeys addObjectsFromArray:[dict allKeys]];
}
NSArray *arrayKeys = [setKeys allObjects];
If you prefer blocks you could use this:
[data enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[setKeys addObjectsFromArray:[obj allKeys]];
}];
I have:
boardValue = [NSNumber numberWithInteger: 2];
NSDictionary * dict = [NSDictionary dictionaryWithValuesForKeys: #"sample", #"word", boardValue , #"value", nil];
This is very similar to the following example:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: #"value1", #"key1", #"value2", #"key2", nil];
from Apple documentations at:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsdictionary_Class/Reference/Reference.html
I get the error "too many arguments to method call, expect 1, have 5". What is the problem?
dictionaryWithValuesForKeys: takes an array as an argument, not a variable list of arguments.
(Also, I believe it's an instance method, not a class method, so [NSDictionary dictionaryWithValuesForKeys:args]` won't work.)
Notice your code compared to the documentation. You want to call...
boardValue = [NSNumber numberWithInteger: 2];
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys: #"sample", #"word", boardValue , #"value", nil];
I am not sure what I am doing wrong here? I have tried various combinations to try and copy an array into variable mmm. I am trying to learn how to create a 2D array and then run a loop to place init_array into 10 columns.
// NSMutableArray *mmm = [NSMutableArray arrayWithCapacity: 20];
NSMutableArray *kkk = [NSMutableArray arrayWithObjects: #"a", #"b", #"cat", #"dog", nil];
NSMutableArray *mmm; //= [NSMutableArray arrayWithObjects: #"1", #"2", #"3", #"4", nil];
[mmm arrayByAddingObjectsFromArray:kkk];
NSLog(#"Working: %#",[mmm objectAtIndex:3]);
thanks...
so this works from the given answer:
NSMutableArray *mmm = [NSMutableArray arrayWithCapacity: 20];
NSMutableArray *kkk = [NSMutableArray arrayWithObjects: #"a", #"b", #"cat", #"dog", nil];
[mmm addObjectsFromArray:kkk];
NSLog(#"Working: %#",[mmm objectAtIndex:3]);
arrayByAddingObjectsFromArray: returns a new (autoreleased) NSArray object. What you want is addObjectsFromArray:.
arrayByAddingObjectsFromArray: returns a new NSArray that includes the objects in the receiver followed by the objects in the argument. The code you posted there, with mmm unset, will probably just crash since mmm doesn't point to an NSArray object. If you had assigned an array to mmm, then it would return (#"1", #"2", #"3", #"4", #"a", #"b", #"cat", #"dog") — but you don't assign the result to any variable, so it just goes nowhere. You'd have to do something like NSArray *yetAnotherArray = [mmm arrayByAddingObjectsFromArray:kkk].
If you have an NSMutableArray and you want to add objects from another array, use addObjectsFromArray:.
Can each index of array hold the NSDictionary?
Thank You.
Yes, the value of an NSArray can resolve to an object identifier for an NSDictionary. However, the array doesn't "hold" the NSDictionary, nor can the index of an NSArray be an NSDictionary. An index from an Array is always an integer value.
An NSArray can hold any type of object, so yes, putting an NSDictionary in an NSArray works just fine.
You sure can, here is an example:
NSDictionary *dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:
#"value1", #"key1", #"value2", #"key2", nil];
NSDictionary *dict2 = [[NSDictionary alloc] initWithObjectsAndKeys:
#"Billy", #"Goat", #"Rover", #"Dog", nil];
NSArray *arrayWithDictionaries = [[NSArray alloc] initWithObjects:
dict1, dict2, nil];
[dict1 release];
[dict2 release];
I have a NSMutableArray with 30 dictionaries inside of it. Each contains a name and a value. I currently have the names sorted so that the show in a table view alphabetically. However, I'd like to make a UIButton to give the option of STILL DISPLAYING THE NAMES, but ordered by the value. The value doesn't need to be displayed. Also, where in the .m/.h files would these codes be placed? THANKS!
To be able to help you I need a test data to work with. Let's say your array with data looks like this:
NSMutableArray *aMutableArray = [NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:
#"A", #"name",
[NSNumber numberWithInt:6], #"value", nil],
[NSDictionary dictionaryWithObjectsAndKeys:
#"C", #"name",
[NSNumber numberWithInt:2], #"value", nil],
[NSDictionary dictionaryWithObjectsAndKeys:
#"B", #"name",
[NSNumber numberWithInt:4], #"value", nil], nil];
In this case case here's how you sort it by value:
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"value" ascending:YES];
[aMutableArray sortUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
As for where to place this code place it wherever you need the reordering to happen (in the implementation [.m] file of course).
HTH and if there will be any questions ask them in the comments.