Is NSDictionary's objectForKey look up reference or value based? - objective-c

I have a NSMutableDictionary instance and the keys I'm using are NSNumber* type.
In the scenario I have, I'm trying to use 'objectForKey' to retrieve an object in my dictionary that I know is present. But I keep getting nil for the result unless I make convert the key from NSNumber to NSString.
NSNumber *itemId = [NSNumber numberWithInt:5];
id *existingItem = [forRemovalLookup objectForKey:itemId];
if (existingItem == nil)
{
// expected: shouldn't be nil
NSLog(#"!!!!!Not expecting this to be nil");
}
Is there another operation I should use to test for the presence of a specific key in a dictionary?

It would work, but only if [itemID hash] was equal to the key's hash, and if [itemID isEqual:] returned true when compared against the key in question. I think an NSNumber's hash is simply the number it holds, but the hash of a string would be completely different even if it was just a string representation of the same number. From memory, the hash of a string is calculated by multiplying each character value by the value of an accumulator times by a certain amount.
There might be something else I'm missing, but there was a discussion on the Cocoa mailing list about class behaviour inside collection objects and the general consensus was that if a class was to hold well in a collection it should correctly return decent values for -hash and -isEqual:.
I know this answer doesn't really help you in this situation, but it may shed some light on how dictionary collections work in Cocoa.

Related

Return a key : value pair from a method for use in NSDictionary

I understand I can return an NSDictionary by doing
- (NSDictionary *)keyWithValue {
return #{#"key" : #"value"};
}
but how can I return that without the enclosing #{} dictionary?
There is no tuples in Objective C unlike in Swift, Python etc. So the common way to return 2 different objects is to return an array or a dictionary.
You also can try something like:
- (NSString *)keyWithValue:(NSString**)value {
*value = #"value";
return #"key";
}
It should be used following way:
NSString *v;
NSString *k = [self keyWithValue:&v];
// now v contains #"value"
Objective-C, like C before it, doesn't allow the return of multiple values from a method. (Essentially, although a method or function can accept any number of arguments as input, it can only have a single return value.) There are historical and implementation reasons for this design but it can be frustrating when you simply have a pair/tuple to return.
If you have a method that has two distinct "results" that you need to return to the caller, you have a few choices. The very simplest in your case is to do something like what you are doing here and "wrapping" the values in a dictionary. You could similarly wrap them in a two-value array (which is a little less good since it relies on an implicit contract between caller and callee that there will be exactly two items in the array).
However, a clean and fairly standard approach here would be to create a small class with only two properties on it, and create, fill in, and return that instance with your pair of values. This arguably uses less runtime overhead than a collection object, and has the nice benefit of being semantically explicit and easy to understand for anyone else looking at it.
(There is yet another way, which involves passing pointers as arguments that are "outparams", but that's only idiomatic in rare circumstances in ObjC and I wouldn't recommend it here.)
There is no way to return a key value pair without a dictionary because that is the definition of the dictionary data structure. From apple docs:
The NSDictionary class declares the programmatic interface to objects that manage immutable associations of keys and values
You access the value with
[myDictionary objectForKey:#"myKey"];
If you want to use the returned key-value pair in another dictionary
NSMutableDictionary *otherDict = [NSMutableDictionary alloc] init];
[otherDict setObject:[myDictionary objectForKey:#"myKey"] forKey:#"myKey"];

How to replace a value of an integer in an NSMutableDictionary?

I have a time sensitive section of my code which stores, retrieves, and replaces values in an NSMutableDictionary. The problem is that I need to store objects, not primitives, and objects need to be allocated on the heap. However, once there exists an object in the dictionary and I want to replace the value, instead of allocating another number, can't I simply replace the current heap literal, wherever it may be, with the new int?
Problem is, I have been storing NSNumbers and I cannot change the value of an NSNumber because they are immutable.
Currently, I use the #() wrapper operator to create an NSNumber, which I believe must be copied to the heap to be stored in the dictionary.
-(void)setInt: (int)value For: (NSString *)key {
[self.dictionary setValue:#(value) forKey:key];
}
I would imagine that replacing an object's primitive in C would be easy:
-(void)setInt: (int)value For: (NSString *)key {
SomeMutableIntClass * oldValue;
oldValue = (SomeMutableIntClass *) [self.dictionary objectForKey:key];
oldValue.int = value; // Direct copy like a pointer since int is primitive
}
I am wondering if I should just make an Integer class with one int property. Even if I went about making my own class would this actually be faster and require less allocing that my current code?
I use the #() wrapper operator to create an NSNumber, which I believe must be copied to the heap to be stored in the dictionary.
This is correct.
I would imagine that replacing an object's primitive in C would be easy:
That is correct too. Unfortunately, since NSNumber is immutable, you need to write your own SomeMutableIntClass.
Even if I went about making my own class would this actually be faster and require less allocing that my current code?
Adding mutability may help, but the answer depends on the values that you store in your mutable dictionary. If you keep storing the same numbers over and over, you may be getting cached instances of your wrappers, without additional allocations.
Note: your current program assumes that all keys will be present in the dictionary. If this is not true, you would need to add nil checking of oldValue to your setInt: method.

Can a block be used as a value for notFoundMarker in iOS?

NSDictionary has the following method signature:
- (NSArray *)objectsForKeys:(NSArray *)keys notFoundMarker:(id)anObject;
My question(s):
Is nil a reasonable default to use for notFoundMarker?
Is it possible to get the key (for which no value was found) back as the notFoundMarker itself? This is useful if the key and value were are different object types, and lets one know what's still missing.
Can a block be used as the value for notFoundMarker, will it actually run? Or will the block's stack-allocated-address simply be returned?
What are some really bad things to try and use as the value for notFoundMarker?
As pointed out in the comments below, you should use [NSNull null]
Probably not without writing your own wrapper method or some sort of category to do this. That said, if you just want to know what key wasn't found, you can simply look at the array of keys that you passed in, as the indexes will match up.
You can certainly use a block. I don't think it will be run. This should be very easy to test though (simply define a block that logs something out to the console and try it).
This really depends on the context and what you're doing to do with the returned array. There's nothing that's inherently bad to put in an array and return, but I'd question the decision to use anything that doesn't match the types of the objects you're expecting to be returned for keys that are found in the NSDictionary.
EDIT:
In fact you could probably achieve what you want for 2. like this:
NSMutableArray *objects = [myDictionary objectsForKeys:myKeys notfoundMarker:[NSNull null]];
for (int ii = 0; ii < [objects count]; ii++ ) {
if ([objects objectAtIndex:ii] == [NSNull null]) {
[objects insertObject:[myKeys objectAtIndex:ii] atIndex:ii];
}
}

Objective-C for Dummies: How do I get a value out of NSDictionary?

so I'm having the most difficult of time pulling values out of an NSDictionary. Right now I just have a dictionary that is populated from a JSON call and it only contains a key named 'Success' with a value of 0 or 1.
How do I do a conditional on that value to check if its 0 or 1? I've tried a bunch of things, but I'm not getting anywhere. Here's my current code:
[[jsonDictionary objectForKey:#"Success"] isEqualToNumber:1]
I'm getting passing argument 1 of 'isEqualToNumber:' makes pointer from integer without a cast' as a warning, and the app crashes when it hits that line anyway.
And a subquestion, what's the difference between objectForKey and valueForKey? Which one should I use by default?
Anyway, this noob in Objective-C would truly appreciate some help on this. Thanks in advance!
Since dictionaries contain Objective-C objects, an entry containing a number is an NSNumber instance. NSNumber provides a convenience method, -intValue, for extracting its underlying int value:
if ([[jsonDictionary objectForKey:#"Success"] intValue] == 1) { … }
Note that NSNumber has other convenience methods for extracting its underlying value as other C data types.
In most cases, you should use -objectForKey: instead of -valueForKey:. The former is the canonical method to obtain an entry in the dictionary and is declared in NSDictionary. The latter is declared in NSObject and is used in Key-Value Coding contexts, where the key must be a valid KVC key, and there’s additional processing — for instance, if you’re using -valueForKey: in a dictionary with a key that starts with #, that character is stripped from the key and [super valueForKey:key] is called.
The number 1 is not an object pointer. Use an NSNumber instance instead if you want to use a number in an NSDictionary.
[[jsonDictionary objectForKey:#"Success"]
isEqualToNumber:[NSNumber numberWithInteger:1]]
[[jsonDictionary objectForKey:#"Success"] isEqualToNumber: [NSNumber numberWithInt:1]]
Number and Value Programming Topics: Using Numbers
NSNumber: What is the point ?
You can get the value of dictionary in different ways like checking
the value first.
Solution 1: Using simple if statement.
int value = 0;
if ([[jsonDictionary objectForKey:#"Success"]intValue]==1){
value = [[jsonDictionary objectForKey:#"Success"]intValue];
}
Solution 2: Using ternary operator
value = ([[jsonDictionary objectForKey:#"Success"]intValue]==1) ? 1:0;

Implementing an NSOutlineView to edit the contents of a plist file

I'm writing a game using cocos2d-iphone and our stages are defined in .plist files. However, the files are growing large - so I've developed an editor that adds some structure to the process and breaks most of the plist down into fixed fields. However, some elements still require plist editor type functionality, so I have implemented an NSOutlineView on the panels that show 'other parameters'. I am attempting to duplicate the functionality from XCode's 'Property List Editor'.
I've implemented the following system; http://www.stupendous.net/archives/2009/01/11/nsoutlineview-example/
This is very close to what I need, but there is a problem that I've spent most of today attempting to solve. Values for the key column are calculated 'backwards' from the selected item by finding the parent dictionary and using;
return [[parentObject allKeysForObject:item] objectAtIndex:0];
However, when there are multiple items with the same value within a given dictionary in the tree, this statement always returns the first item that has that value (it appears to compare the strings using isEqualToString: or hash values). This leads to the key column showing 'item1, item1, item1' instead of item1, item2, item3 (where items 1-3 all have value ''). I next tried;
-(NSString*)keyFromDictionary:(NSDictionary*)dict forItem:(id)item
{
for( uint i = 0; i < [[dict allKeys] count]; i++ ) {
id object = [dict objectForKey:[[dict allKeys] objectAtIndex:i]];
if ( &object == &item ) {
return [[dict allKeys] objectAtIndex:i];
}
}
return nil;
}
But this always returns nil. I was hoping that somebody with a bit more experience with NSOutlineView might be able to provide a better solution. While this problem only appears once in the linked example, I've had to use this a number of times when deleting items from dictionaries for instance. Any help would be greatly appreciated.
return [[parentObject allKeysForObject:item] objectAtIndex:0];
However, when there are multiple items with the same value within a given dictionary in the tree, this statement always returns the first item that has that value …
Well, yeah. That's what you told it to do: “Get me all the keys for this value; get me the first item in the array; return that”.
… this statement always returns the first item that has that value (it appears to compare the strings using isEqualToString: or hash values).
It's not that statement that's doing it; it's how dictionaries work: Each key can only be in the dictionary once and can only have exactly one object as its value, and this is enforced using the hash of the key and by sending the keys isEqual: messages (not the NSString-specific isEqualToString:—keys are not required to be strings*).
The values, on the other hand, are not uniquified. Any number of keys can have the same value. That's why going from values to keys—and especially to a key—is so problematic.
*Not in NSDictionary, anyway. When you attempt to generate the plist output, it will barf if the dictionary contains any non-string keys.
I next tried;
-(NSString*)keyFromDictionary:(NSDictionary*)dict forItem:(id)item
{
for( uint i = 0; i < [[dict allKeys] count]; i++ ) {
id object = [dict objectForKey:[[dict allKeys] objectAtIndex:i]];
if ( &object == &item ) {
return [[dict allKeys] objectAtIndex:i];
}
}
return nil;
}
But this always returns nil.
That's the least of that code's problems.
First, when iterating on an NSArray, you generally should not use indexes unless you absolutely need to. It's much cleaner to use fast enumeration.
Second, when you do need indexes into an NSArray, the correct type is NSUInteger. Don't mix and match types when you can help it.
Third, I don't know what you meant to do with the address-of operator there, but what you actually did was take the address of those two variables. Thus, you compared whether the local variable object is the same variable as the argument variable item. Since they're not the same variable, that test always returns false, which is why you never return an object—the only other exit point returns nil, so that's what always happens.
The problem with this code and the earlier one-liner is that you're attempting to go from a value to a single key, which is contrary to how dictionaries work: Only the keys are unique; any number of keys can have the same value.
You need to use something else as the items. Using the keys as the items would be one way; making a model object to represent each row would be another.
If you go the model-object route, don't forget to prevent multiple rows in the same virtual dictionary from having the same key. An NSMutableSet plus implementing hash and isEqual: would help with that.
You probably should also make the same change to your handling of arrays.
To clarify, I eventually resolved this problem by creating proxy objects for each of the collections in the plist file (so, for every NSMutableArray or NSMutableDictionary). This meant that I essentially mirrored the Plist structure and included references back to the original objects at each level. This allowed me to store the array index for each object or the dictionary key, so when saving items back from the outline view to the Plist structures, I used the 'key' or 'index' properties on the proxy object.