I'm using json-framework to create a NSDictionary out of a JSON response. That much works wonderfully.
Now, within this JSON payload are one or more objects - let's call them X. Sort of like this in XML:
<OBJECTS>
<X>
...
</x>
<X>
...
</X>
<X>
...
</X>
</OBJECTS>
When I look in the aforementioned NSDictionary object for all Xs, like so:
NSDictionary *results = [[dict objectForKey:#"OBJECTS"] objectForKey:#"X"];
Or even:
NSDictionary *results = [dict valueForKeyPath:#"OBJECTS.X"];
I get, according to gdb, a NSCFArray of NSDictionary objects. (Yes, I smell something funny here too, but more on this in a moment.)
When there is only one object named X, I get back an honest-to-goodness NSDictionary.
So ... what should I do to make this behave consistently, no matter how many Xs there are?
At first glance, I'd think I just change results to be NSArray *, but what happens when I want to fast enumerate over the results? Right now I do this:
for (NSDictionary *result in results)
In the NSCFArray case (multiple Xs), I get back an individual NSDictionary for each X. In the single X case, I get back the one NSDictionary, except now my perspective is one level too deep. In other words, instead of this (contrived example):
(gdb) po results
<NSCFArray 0xd4a940>(
{
foo = {
bar = "something";
};
}
{
foo = {
bar = "something else";
};
}
)
I get this:
(gdb) po results
{
foo = {
bar = "something";
};
}
Clues welcome/appreciated! You may even ask if it's necessary to have to break this apart at all, but for now let's suppose this trip is really necessary. (Still, I'm happy to be persuaded otherwise, if someone feels strongly enough about it.)
Ultimately, at the end of the day, I want to have a NSArray of NSDictionary objects.
I am not familiar with JSON or the json-framework, but clearly objectForKey cannot be used to access the X's since they all have the same key.
If you know that objectForKey:#"OBJECTS" will return either an NSDictionary (single element) or an NSArray of NSDictionarys (multiple X elements), then you could do something like:
if ( ![results isKindOfClass:[NSArray class]] ) {
results =[NSArray arrayWithObject:results];
}
That will get you a consistent result, assuming you understand exactly how the json-framework will behave. It will be somewhat fragile, if the elements ever return an array of entries instead of an NSDitionary then it will all fall apart.
There may be a configuration setting for the json-framework that lets you control how it behaves in this case and that would be preferable.
NSDictionary *results = [[dict objectForKey:#"OBJECTS"] objectForKey:#"X"];
and
NSDictionary *results = [dict valueForKeyPath:#"OBJECTS.X"];
The above two are exactly same. The first operation is a little costlier operation than the second one.
Here's what I ended up doing:
// Get the JSON response and return the result:
NSString *jsonString = [NSString stringWithContentsOfURL:url];
NSDictionary *dict = [jsonString JSONValue];
// If OBJECTS.X gets a dictionary (one value), make an array of one.
id myObject = [dict valueForKeyPath:#"OBJECTS.X"];
if ([myObject isKindOfClass:[NSDictionary class]]) {
results = [NSArray arrayWithObject:myObject];
} else {
results = myObject;
}
It's worth pointing out that JSONValue can also return a dictionary or an array. However, I'm looking inside the dictionary post-assignment (OBJECTS.X).
I don't think your two code examples,
NSDictionary *results = [[dict objectForKey:#"OBJECTS"] objectForKey:#"X"];
and
NSDictionary *results = [dict valueForKeyPath:#"OBJECTS.X"];
are equivalent. Key-Value-Coding has some extra smarts to work with collections, which might be throwing you off here. (An example can be found in the blog post Theocacao: NSArray and KVC by Scott Stevenson.)
If you revert back to the former you might get the behaviour you expect.
Related
void CountlyRecordEventSegmentationCountSum(const char * key, const char * segmentation, int count, double sum)
{
NSString * seg = CreateNSString(segmentation);
NSArray * entries = [seg componentsSeparatedByString:#"`"];
NSDictionary * dict = [NSDictionary dictionary];
for (id entry in entries)
{
NSArray * keyValue = [entry componentsSeparatedByString:#"|"];
[dict setValue:[keyValue objectAtIndex:1] forKey:[keyValue objectAtIndex:0]];
}
[[Countly sharedInstance] recordEvent:CreateNSString(key) segmentation:dict count:count sum:sum];
}
I put "?" in the title because I'm not entirely sure if the problem is in the code above but that's my best guess. I'm integrating Countly iOS plugin with Unity and one of Countly plugin's methods take NSDictionary * as argument. As I don't know how to send a dictionary from C# to Objective-C I'm storing my dict in a string, sending it to Objective-C and then recreating the dictionary (the code above).
But that's probably even not relevant. I know EXC_BAD_ACCESS usually has something to do with unfreed resources or sth so maybe you can see what I'm doing wrong (I don't know Objective-C at all, just writing a few lines needed by the plugin).
Edit:
From Unity sample:
// Converts C style string to NSString
NSString * CreateNSString (const char * string)
{
if (string)
return [NSString stringWithUTF8String: string];
else
return [NSString stringWithUTF8String: ""];
}
The error you've made is that you are trying to modify immutable version of NSDictionary.
One cannot modify contents of the NSDictionary after it's initialization. You should use NSMutableDictionary instead.
Here is a documentation on NSMutableDictionary.
And here is an example of how to create mutable version of an immutable object that conforms to NSMutableCopying protocol.
You need to be using NSMutableDictionary, you can't modify an NSDictionary.
Also, you should use setObject:forKey: because setValue:forKey: is a KVC method. It happens to do the same thing on an NSMutableDictionary for most keys, but it is marginally slower.
Finally, you should check that [keyValue count] >= 2 before trying to access the objects at indexes 0 and 1.
Edit Also, CreateNSString() looks suspicious. It might be either leaking or prematurely releasing the string. But you need to post the code. In any case, I'd use
seg = [NSString stringWithUTF8String: segment];
or, other appropriate method if segment is not encoded in UTF-8.
NSDictionary has objectForKey but it's case-sentive for keys. There is No function available like
- (id)objectForKey:(id)aKey options:(id) options;
where in options you can pass "NSCaseInsensitiveSearch"
To get key's from NSDictionary which is case-insesitive one can use the following code written below.
You need to add Category of NSDictionary Class with this functionality
- (id)objectForCaseInsensitiveKey:(NSString *)key {
NSArray *allKeys = [self allKeys];
for (NSString *str in allKeys) {
if ([key caseInsensitiveCompare:str] == NSOrderedSame) {
return [self objectForKey:str];
}
}
return nil;
}
This isn't included for a couple of reasons:
NSDictionary uses hash equality, and for pretty much any good hashing algorithm, any variation in the source string results in a different hash.
More importantly, NSDictionary keys are not strings. Any object that conforms to NSCopying can be a dictionary key, and that includes a whole lot more than strings. What would a case-insensitive comparison of an NSNumber with an NSBezierPath look like?
Many of the answers here offer solutions that amount to transforming the dictionary into an array and iterating over it. That works, and if you just need this as a one-off, that's fine. But that solution is kinda ugly and has bad performance characteristics. If this were something I needed a lot (say, enough to create an NSDictionary category), I would want to solve it properly, at the data structure level.
What you want is a class that wraps an NSDictionary, only allows strings for keys and automatically lowercases keys as they are given (and possibly also remembers the original key if you need a two-way mapping). This would be fairly simple to implement and is a much cleaner design. It's too heavy for a one-off, but if this is something you're doing a lot, I think it's worth doing cleanly.
The correct answer is that you should use case-folded keys as dictionary keys. This is not the same as converting them to upper or lower case and it won't destroy the O(1) average case search/insert complexity.
Unfortunately, Cocoa doesn't seem to have an appropriate NSString method to case-fold a string, but Core Foundation has CFStringFold() which you can use for that purpose. Let's write a short function to do the necessary work:
NSString *foldedString(NSString *s, NSLocale *locale)
{
CFMutableStringRef ret = CFStringCreateMutableCopy(kCFAllocatorDefault, 0,
(__bridge CFStringRef)s);
CFStringNormalize(ret, kCFStringNormalizationFormD);
CFStringFold(ret, kCFCompareCaseInsensitive, (__bridge CFLocaleRef)locale);
return (__bridge_transfer NSString *)ret;
}
Note that the locale argument is important. If you specify NULL, you will get the current system locale. This will be fine in most cases, but Turkish users might be surprised that "I" matches "i" rather than "ı". You might therefore want to pass [NSLocale currentLocale], and if you're saving the results you might also want to save the locale identifier and create the locale from that.
So, when adding to the dictionary, you now need to do
[dict setObject:obj forKey:foldedString(myKey, locale)];
and to look up again
[dict objectForKey:foldedString(myKey, locale)];
One final observation is that you might wish to store the case-folded keys alongside the original values, then you don't have to fold them on every access to the dictionary.
In the code written below, I search for a actual key for a input key. So , if input key=#"naMe" then the actual key=#"name".
NSDictionary *dic=[NSDictionary dictionaryWithObjectsAndKeys:#"John",#"Name",#"123456",#"empId", nil];
NSString *key=#"naMe";
NSString *name=[dic objectForKey:key];
if(name==nil){
NSPredicate *searchPred=[NSPredicate predicateWithFormat:#"self LIKE[cd] %#",key];
NSArray *searchedKeys=[[dic allKeys] filteredArrayUsingPredicate:searchPred];
if(searchedKeys.count>0){
name=[dic objectForKey:[searchedKeys objectAtIndex:0]];
}
}
NSLog(#"Name = %#",name);
Many answers are correct, but here's a more example:
NSDictionary* dict= #{ #"hello" : #"Hey" };
NSArray* keys= [dict allKeys];
NSUInteger index=[keys indexOfObjectPassingTest: ^BOOL (id obj, NSUInteger index, BOOL* stop)
{
if( [obj caseInsensitiveCompare: #"Hello"]==NSOrderedSame)
{
*stop= YES;
return YES;
}
else
{
return NO;
}
}];
Personally I find this method easier, but everyone has his programming style.
EDIT
A less readable but shorter solution:
NSDictionary* dict= #{ #"hello" : #"Hey" };
NSArray* keys= [dict allKeys];
NSUInteger index=[keys indexOfObjectPassingTest: ^BOOL (id obj, NSUInteger index, BOOL* stop)
{
return *stop= [obj caseInsensitiveCompare: #"Hello"]==NSOrderedSame ;
}];
If you are only storing into, and retrieving from, the NSDictionary in one place (maybe two or three), you could use
[myString lowercaseString]
in both. The more rigorous answers are useful if the dictionary object is used all over your code.
The way I understand it one of the things special about Objective C is that you can send messages to NULL and it will just ignore them instead of crashing.
Why is it that NSArray doesn't just return a NULL object if the index requested is out of bounds instead of causing a NSRangeException?
What I would expect from Objective C and NSArray is the following.
NSArray *array = [NSArray arrayWithObjects:#"Object 1", #"Object 2", #"Object 3", nil];
for (int i = 0; i < 5; i++) {
NSString *string = [array objectAtIndex:i];
if (string) {
NSLog(#"Object: %#",string);
}
}
Allowing me to access indexes of the array with don't contain objects and simply returning NULL. Then I can check if the object exists. I can do this in other places such as checking if the object has been instantiated.
NSArray *array;
if (!array) {
array = [NSArray array];
}
I realize this is a theory based question but I'm curious :)
Messages to nil is a language level feature, not a framework API feature.
That NSArray (and other classes) throw an exception to indicate programmer error is a conscious design decision that was made when the API was designed (~1993). As both Kevin and Richard indicate, nil eats message is not erroneous in anyway whereas out of bounds is very much a case of erroneous input.
Where would you draw the line? Should substringWithRange: accept any old input? How about setObject:atIndex:?
By making NSArray #throw on bounds exceptions for indexOfObject: it makes the API consistent; any index (or range) that is out of bound will #throw.
You could use an NSMutableDictionary to accomplish what you're trying to do. Set the keys to be NSNumber indexes. Invoking objectForKey on a nonexistent key (out of our imaginary bounds or removed) will return nil.
I'll use an example from JavaScript to help clarify my question. Let's assume I have the following object:
sports = {
soccer: {...},
basketball: {...},
baseball: {...}
}
If at some point in my script I have a variable, sportString, that simply holds a string, I can dynamically call one of the sports objects in the following way:
sports[sportString];
This frees me from having to use a bunch of nested if statements, testing the value of the string such as:
if(sportString === 'soccer'){
sports.soccer;
}else if(sportString === 'basketball){....
So, my question is how can I accomplish something similar to sports[sportString] in Objective-C, if sportString is an NSString object?
Use an NSDictionary as your sports object. Then you can do lookups like this:
[sports objectForKey: sportsString];
The people saying you should use NSDictionary for general key/value storage are 100 % right. However, I think it’s useful to know that you can call a message specified by a string:
SEL selector = NSSelectorFromString(#"foo"); // Or #selector(foo) if you know it at compile time
id value = [object performSelector:selector];
You can also use selectors with up to two arguments, as long as they take objects:
SEL selector2 = NSSelectorFromString(#"setFoo:");
[object performSelector:selector2 withObject:value];
It’s possible to invoke arbitrary methods using IMPs or casting objc_msgSend(), but now I’m getting way beyond the scope of your actual question. :-)
Your JavaScript object sports would typically be an NSDictionary or NSMutableDictionary.
Example:
NSMutableDictionary *sports = [NSMutableDictionary dictionary];
[sports setObject:#"Foo" forKey:#"soccer"];
[sports setObject:#"Bar" forKey:#"basketball"];
NSString *sportString = #"soccer";
NSString *sportValue = [sports objectForKey:sportString];
NSLog(#"%#", sportValue); //logs "Foo"
I'm parsing some input which produces a tree structure containing NSDictionary instances on the branches and NSString instance at the nodes.
After parsing, the whole structure should be immutable. I feel like I'm jumping through hoops to create the structure and then make sure it's immutable when it's returned from my method.
We can probably all relate to the input I'm parsing, since it's a query string from a URL. In a string like this:
a=foo&b=bar&a=zip
We expect a structure like this:
NSDictionary {
"a" => NSDictionary {
0 => "foo",
1 => "zip"
},
"b" => "bar"
}
I'm keeping it just two-dimensional in this example for brevity, though in the real-world we sometimes see var[key1][key2]=value&var[key1][key3]=value2 type structures. The code hasn't evolved that far just yet.
Currently I do this:
- (NSDictionary *)parseQuery:(NSString *)queryString {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *pairs = [queryString componentsSeparatedByString:#"&"];
for (NSString *pair in pairs) {
NSRange eqRange = [pair rangeOfString:#"="];
NSString *key;
id value;
// If the parameter is a key without a specified value
if (eqRange.location == NSNotFound) {
key = [pair stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
value = #"";
} else {
// Else determine both key and value
key = [[pair substringToIndex:eqRange.location] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if ([pair length] > eqRange.location + 1) {
value = [[pair substringFromIndex:eqRange.location + 1] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
} else {
value = #"";
}
}
// Parameter already exists, it must be a dictionary
if (nil != [params objectForKey:key]) {
id existingValue = [params objectForKey:key];
if (![existingValue isKindOfClass:[NSDictionary class]]) {
value = [NSDictionary dictionaryWithObjectsAndKeys:existingValue, [NSNumber numberWithInt:0], value, [NSNumber numberWithInt:1], nil];
} else {
// FIXME: There must be a more elegant way to build a nested dictionary where the end result is immutable?
NSMutableDictionary *newValue = [NSMutableDictionary dictionaryWithDictionary:existingValue];
[newValue setObject:value forKey:[NSNumber numberWithInt:[newValue count]]];
value = [NSDictionary dictionaryWithDictionary:newValue];
}
}
[params setObject:value forKey:key];
}
return [NSDictionary dictionaryWithDictionary:params];
}
If you look at the bit where I've added FIXME it feels awfully clumsy, pulling out the existing dictionary, creating an immutable version of it, adding the new value, then creating an immutable dictionary from that to set back in place. Expensive and unnecessary?
I'm not sure if there are any Cocoa-specific design patterns I can follow here?
Expensive and unnecessary?
Yes. Apple's Cocoa APIs regularly say they return an immutable object, but actually return a mutable subclass that's been cast to the immutable version. This is a standard operating procedure and an accepted Cocoa design principle. You just trust that your clients aren't going to cast it back to a mutable version and change things from underneath you.
From Cocoa Core Competencies: Object Mutability:
Receiving Mutable Objects
When you call a method and receive an object in return, the object could be mutable even if the method’s return type characterizes it as immutable. There is nothing to prevent a class from declaring a method to return an immutable object but returning a mutable object in its implementation. Although you could use introspection to determine whether a received object is actually mutable or immutable, you shouldn’t. Always use the return type of an object to judge its mutability.
See also: Cocoa Fundamentals Guide: Cocoa Objects.