Using valueForKeyPath on NSDictionary if a key starts the # symbol? - iphone

I want to use valueForKeyPath on my NSDictionary, but the problem is that one of the keys is a string that starts with the # symbol. I have no control over the naming of the key.
I'm having problems trying to create the key path as I'm getting a format exception, even when trying to escape the # symbol:
This works fine:
[[[dict objectForKey:#"key1"] objectForKey:#"#specialKey"] objectForKey:#"key3"]
However none of these work:
[dict valueForKeyPath:#"key1.#specialKey.key3"]
[dict valueForKeyPath:#"key1.##specialKey.key3"]
Any ideas?
Thanks,
Mike

you shouldn't be using # signs with your key names if you want to use key value coding.
apple's guidelines for key names are as follows:
Keys must use ASCII encoding, begin
with a lowercase letter, and may not
contain whitespace.
You'll have to find a workaround to reformat the key string whereever you're getting your keys from to be KVC compliant.

Just to update this old question a little...
The reason that these:
[dict valueForKeyPath:#"key1.#specialKey.key3"]
[dict valueForKeyPath:#"key1.##specialKey.key3"]
...fail is that any "#" symbols in a key path are interpreted as being collection's operators as with:
[dict valueForKeyPath:#"key1.#sum.key3"] // returns the sum of all 'key3' values
[dict valueForKeyPath:#"key1.#avg.key3"] // returns the average of all 'key3' values
The nested key calls:
[[[dict objectForKey:#"key1"] objectForKey:#"#specialKey"] objectForKey:#"key3"]
... work because a single key is not processed as a key path.

If you have no control over the naming, how about adding a category with a properly named key that simply returns/sets the weird key?

I see that there are 2 ways
Swizzle
You can swizzle the valueForKeyPath on NSDictionary to remove the # symbol, remember to account for #sum, #average, ...
Override if you're using Mantle
Override + (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary on MTLJSONAdapter, traverse all the keys and remove the # symbol

In my humble opinion, the whole discussion here goes the wrong way Accessing entries in an NSDictionary via key paths - is simply not part of KVC protocol.
KVC defines how to name your properties of an object, so that KVC can work. an entry in an NSDictionary is not a property, and has no name. NSDictionary adds its bit of magic to the KVC-like behaviour, by "pretending" the keys of its entries are like 'properties' of the dictionary.
Alas, properties have different naming conventions and limitations than dictionary keys.
If you cannot force the dictionary keys to conform with KVC-supported property names - break your key paths, and use the accessors instead where in doubt.
That, I think, should be the safest way to go. KVC is generally a "nicety" being able to shorten your code - but it does NOT provide any functionality you cannot have otherwise (as you demonstrated yourself).

Related

using # as part of the key in the NSDictionary string

I have the following code, however when I tried to get the value out of this dictionary using this key it crashes. When I remove the #, it works just fine. Any idea?
[self.replacementFBMsgDictionary_ setValue:profileItem.fbId forKey:[NSString stringWithFormat:#"#%#", profileItem.username]];
The thing is, "#" at the beginning of a key has special significance in Key-Value Coding — it's used for collection operators like #count. As such, KVC keys cannot begin with that string. So instead of the KVC setValue:forKey:, you'll have to use NSDictionary's primitive setObject:forKey:.
If profile.username is an NSString, why are you doing that? Why not just use that for the key?
[self.replacementFBMsgDictionary_ setValue:profileItem.fbId forKey:profileItem.username];

reading an NSDictionary with a switch statement

I am trying to figure out (if possible) how to read through NSDictionay key/values with a switch statement instead of a for loop.
currently I am doing this
for (id key in seriesData) {
NSLog(#"key: %#, value: %#", key, [seriesData objectForKey:key]);
}
However I would like it to be a switch statement where I look for the key, if the key is found then i put that value into a variable i will use later.
hope this makes sense, any help would be greatly appreciated.
The C switch statement only applies to integer-like keys (which includes enum). It is not possible to use switch with Objective-C objects, such as those found in an NSDictionary.
If you're familiar with using blocks, you could use one the block based enumeration methods enumerateKeysAndObjectsUsingBlock for example.
Also, if you're looking to "filter" keys; as in there are specific subset of keys, (keys starting with "A" for example), you could use one of the predicate methods like keysOfEntriesPassingTest.
All of these are documented on the NSDictionary class reference.

Accessing array/set of objects with keys

I want to be able to add objects to an NSArray and access them with Keys. Currently the way im doing it is creating a seperate NSDictionary of key-value pairs where the value is an integer number representing the index in my NSArray. This seems like an extra step to me.
If my understanding of NSDictionary is correct, only 'values' can be stored: a pointer to an object cannot.
Surely there must be an equivalent NSDictionary type function that allows objects to be stored and accessed with a key? I have looked through the documentation, but cant seem to find any answers, unless im missing something obvious.
NSDictionary is to store key value pairs. if you are adding key value pair after you created the dictioanry, use NSMutableDictionary class . example,
[dictionaryObject setObject:#"" forKey:#"abc"];
You can store objects in NSDictionary and can be accessed via keys...
In short, no.
An array (NSArray) is an ordered collection of references to objects, so simply said, an ordered collection of objects.
As opposed to dictionaries, which are unordered and values are accessed by keys.
You understanding of collections is probably wrong, you don't store values, but pointers (references).
The extra step is necessary if you need to store the references in an array, but in this case, you should consider using a dictionary. An option is to use keys that take care of the order.
For example :
[myDictionary objectForKey:#"1"];
could be an equivalent of :
[myArray objectAtIndex:1];
Thats wrong, you can store objects in a NSDictionary. Look at the method dictionaryWithObjects:forKeys: or dictionaryWithObjectsAndKeys:
I have no experience in Cocoa but looking at the documentation it seems like NSDictionary (or at least NSMutableDictionary) should handle your request (without you using NSArray).
I think I understand your problem. My suggestion for you is to use NSMutableArray and macros, like:
NSMutableArray *array=[[NSMutableArray alloc]init];
#define SOME_MACRO 0
id someObject;
[array insertObject:someObject atIndex:SOME_MACRO];
id getterObject=[array objectAtIndex:SOME_MACRO];
Of course define the macros in the header file.

How to use an object as a key in Objective-C

I would like to use a custom object as a key in a hash-like structure. I've tried using NS[Mutable]Dictionary but in order for my object to be a key it has to implement the NSCopying protocol. NSDictionary is sending a copy message to all of it's keys as far as I've read. I don't want to implement the protocol (my object is quite complex) nor do I want it to be copied. What are my options? Do I have any?
NSDictionary is toll-free bridged with CFDictionaryRef, but they actually differ in behavior when adding objects. Specifically, NSDictionary's -setObject:forKey: will copy the key, but CFDictionaryRef's CFDictionarySetValue() will not copy the key. This means that if you want to use non-copyable keys, you can use CFDictionarySetValue() instead to add it to the dictionary.
CFDictionarySetValue((CFMutableDictionaryRef)myDict, myKey, myValue);
This will still retain the key, but it won't copy it. And you can use the normal NSDictionary methods for everything else.
Do you need the NSDictionary to retain the object? If not, you can turn it into an NSValue and use that as the key:
NSValue *value = [NSValue valueWithNonretainedObject:yourCustomObject];
[dictionary setObject:someObject forKey:value];
This can get a bit messy but is in alternative to implementing NSCopying.
You can roll your own dictionary. Not really that hard.
Another option is to use a surrogate object, containing a pointer to "the" object. The surrogate would implement the hash and either copy or reference the fields to be compared for isEqual. It could do a basic sanity check to assure the compared fields have not been changed when it's referenced.
You could just do this:
- (id)copyWithZone:(NSZone *)zone {
return [self retain];
}

Extracting the copyable identity of the non-copyable object

I need to use non-copyable objects as keys in the NSMutableDictionary which, by default, is not allowed. I understand the reasons for this not being allowed (retaining the key object, unlike the value object, is undesirable), but it seems like in my particular situation there could be a way around this.
The thing is that I'm only need to query the dictionary using the key's address, i.e. having the lookup predicate
if (providedKey == storedKey)
instead of
if ([providedKey isEqual:storedKey])
would be perfectly sufficient.
Is there a way of extracting the object's reference address (or other form of identity) as a copyable comparable object which I could use as a dictionary key instead of the object itself?
It seems that +[NSValue valueWithPointer:] might be what you want. It stores the pointer itself in an object that conforms to NSCopying, so that you can use it as a dictionary key. Retrieve the pointer using pointerValue.
You could use [NSString stringWithFormat:#"%p", someObject] as your key.
This will create a string with the object's address as a hex value.
You could use an nsnumber that represents the hash of the object.
you can drop down to the CoreFoundation APIs and define you own callbacks (among other things).
NSMutableDictionary is a CFMutableDictionary.
specifically, you want to create a CFMutableDictionary, and define your own CFDictionaryKeyCallBacks.