Extracting the copyable identity of the non-copyable object - iphone

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.

Related

CoreData Object typing won't work

Can someone explain to me why this doesn't work:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = classObject.stringProperty;
But this does:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = [classObject valueForKey:#"stringProperty"];
EDIT:
What's the easiest way to cast the dictionary as my NSManagedObjectClass CoreDataClass so I can access properties directly?
It doesn't work since KVC compliance is not at all what defines classes or makes them castable - the class hierarchy exists for a reason, and just ensuring adherence to certain methods doesn't magically make something an instance of a completely different class. Keep in mind that the dot-accessor syntax is just sugar for a method send, so these two are equivalent:
classObject.stringProperty
[classObject stringProperty]
...and the latter obviously isn't valid for instances of NSDictionary (i.e. [[NSDictionary class] instancesRespondToSelector:#selector(stringProperty)] is NO).
Your latter example works because of the very premise of your question: if something is KVC-compliant for the key stringProperty, and you ask it for a value for that key, then obviously you get something back. Furthermore, both NSDictionary and CoreDataClass respond to the selector -valueForKey:, so the message send actually works at runtime.
The best way to get the two across isn't a "cast" at all - it's a complete conversion, at the property level, of the data involved. You might consider creating a custom -initWith... method on CoreDataClass that lets you instantiate its properties from a dictionary, or finding a way to get your method to return an actual instance of CoreDataClass instead of an NSDictionary.
Note that this solution may differ from the "easiest" way to get the data across, which is effectively to keep doing what you're doing and use -valueForKey: (though preferably without the cast, which is misleading).
Casting objects only appears to work (in the sense that you won't get type-checking errors) because it's a hint to the compiler, but it doesn't actually change anything about what the pointer points to, so you are still pointing to an NSDictionary. This is because, at the end of the day, you are essentially casting a pointer to a pointer, but telling Xcode that you are allowed to send a different set of selectors to it.
For NSManagedObjects, creation from a dictionary depends on a few things, but the recommended way is to make a class method on your custom class which will use NSEntityDescription and you NSManagedObjectContext, and sets the properties from the dictionary to the object:
+(CoreDataClass *) coreDataObjectWithDictionary:(NSDictionary *) spec {
CoreDataClass *myInstance = [NSEntityDescription insertNewObjectForEntityForName: #"CoreDataClass" inManagedObjectContext: [myMOCProvider sharedMOC];
myInstance.someProp = [spec valueForKey:#"someProp"];
}

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];
}

Check for iVar's Names with respect to NSDictionary keys

I am parsing the values of Json object to my data model, and I am trying to figure out if there is any way to compare the name of the iVar's set for are the same with respect to dictionary keys. I am sure there is a good way to do that, but just can't find the method somehow.
Thanks.
It's a little hard to tell what you're asking, but I think you want Key-Value Coding. In particular, setValuesForKeysWithDictionary: will let you pass a dictionary of property or ivar names and set them for you.

What classes can be used as a key in NSDictionary?

Can we use only NSString objects as key in an NSDictionary? How do we know which objects can be used and which cannot?
From the documentation:
In general, a key can be any object (provided that it conforms to the NSCopying protocol), but note that when using key-value coding the key must be a string (see “Key-Value Coding Fundamentals”).
So you can use anything copyable besides strings, but they'll be problematic with KVC. I just use strings for keys to keep things safe, consistent and simple.
You can use anything that conforms to NSCopying. That is, you can use id - type objects, as long as they conform to NSCoding protocol.
In instances where the key is NSString, then isEqualToString: is called for retrieval. Otherwise, isEqual: is called on the object to determine whether the key matches the requested key.
The key (and value for that matter) cannot be nil or NULL. They can, however, be [NSNull null].

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

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).