isEqual of NSManagedObject - How does it determine equality? - objective-c

I'm dealing with an NSMutableDictionary with NSManagedObject keys. Some of the data in my keys are going to be mutated, but the NSMutableDictionary class reference states that the keys are copied when using setObject! And when I want to access values using keys, it's going to compare the key I give it to the keys in the key list.
Sorry if this is a dumb question, but I can't find on the NSManagedObject class reference what isEqual does. Does it compare every value, compare some unique identifiers, compare the memory addresses, or something else?
I've seen some unsure answers here: Should I use == or [NSManagedObject isEqual:] to compare managed objects in the same context?, one of them saying that it compares the hash methods. Hash codes are not necessarily unique identifiers of objects, right?

I see others have answered in the comments - but for the "officiality" of it - here is the proper detailed answer.
Since NSManagedObjcet class does not conform to <NSCoding> protocol - it cannot serve as a key in NSDictionary or NSMutableDictionary.
Rather - you would want to use the objectID property of your managedObject as key in a dictionary. objectID is an NSString, which of course conforms to NSCoding.
Using objectID has another benefit. If you have more than one NSManagedObjectContext, The same entity will have a different instance of the NSManagedOBject for each NSManagedObjectContext. The only way to know they are one - is via their objectID property - which will be isEqual.

Related

To be a key of an NSDictionary, must a class also implement isEqual: and hash?

I understand that a class must implement NSCopying in order to be a key of an NSDictionary, but is implementing isEqual: and hash also necessary or advisable?
If yes, why?
Yes.
Why?
Consider accessing an element of a dictionary, how does NSDictionary find the object associated with a key? By comparing the key value you provide with the keys in the dictionary.
It is a rule when you implement isEqual: that you also implement hash, objects which compare equal must have the same hash. Consider further how the dictionary may organise the storage of the key/value pairs, it uses a hash-based storage structure.
HTH
Addendum
Seeing what I guess is a related question you've also asked I will qualify the the above "Yes":
If a class inherits isEqual: and hash methods which appropriately define equality for itself it need not override the methods with its own versions. In all probability this will not be true if the class directly inherits from NSObject.
You can find it in Documentation:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/index.html
A key-value pair within a dictionary is called an entry. Each entry
consists of one object that represents the key and a second object
that is that key’s value. Within a dictionary, the keys are unique.
That is, no two keys in a single dictionary are equal (as determined
by isEqual:). In general, a key can be any object (provided that it
conforms to the NSCopying protocol—see below), but note that when
using key-value coding the key must be a string (see Key-Value Coding
Fundamentals). Neither a key nor a value can be nil; if you need to
represent a null value in a dictionary, you should use NSNull

Why must the key of an NSDictionary conform to NSCopying

I noticed that for an object to be a key for an NSDictionary it must conform to NSCopying.
Why is this so? I would understand that all keys must implement hash, but why NSCopying?
Because the keys are copied. You wouldn't want a key to be stored as a reference to the very same object you started with, would you? It would be terrible if d[myThing] = myValue retained a reference to a possibly mutable instance myThing. That would mean that the dictionary could get mutated behind its own back.
NSDictionary guaranties that if you store a value with some key x this key is fixed and you can retrieve this value with the equivalent key y (y.isEqual(x) == YES). There are only two possibilities to do so:
Copy keys to prevent them from changing.
Demand that keys are immutable.
Apple decided that for most cases coping keys is better.
In case you need a dictionary were keys are not copied (for example keys do not implement NSCopying or coping is too expensive) you can use NSMapTable.
For example you can use
[NSMapTable strongToStrongObjectsMapTable]
to store keys and values as a strong references.

Objective-C HashMap equivalent

I am trying to convert a piece of Java code which uses a HashMap that contains as a key an object and a value as an object.
private static HashMap<Class<? extends Component>, ComponentType> componentTypes = new HashMap<Class<? extends Component>, ComponentType>();
I've been reading on how to do this with Obj-C but I have not been successful, most people suggest using a NSDictionary, the problem is that they keys need to be strings and I need them as objects. The other option was NSMapTable, however it is not available on iOS. Would someone be able to assist on how I can convert this into an obj-c equivalent?
thanks,
The keys for an NSDictionary do not need to be strings. They can be any object that implements NSCopying. If the object is a custom object, however, it needs to produce sane responses to the -hash and -isEqual: messages, but this is the same as using an object in a Java collection so it shouldn't be much of a challenge.
An NSMutableDictionary (assuming that you also need to set values in the dictionary after its initialization) works in two ways:
As a traditional dictionary/hashmap in which you set values like this:
[myDictionary setObject: theValue forKey: anyObject];
As an object with KVC-compliant properties that happen to be defined dynamically:
[myDictionary setValue: theValue forKey: aString];
If the key is an NSString, then the two are interchangeable, with the exception that you can't set an object to nil with setObject:forKey:, but you can pass nil to setValue:forKey:.
You want to use an NSDictionary. You say that
they keys need to be strings and I need them as objects
The keys to an NSDictionary don't need to be strings -- they can be any object that conforms to the NSCopying protocol.
From NSDictionary reference
A key-value pair within a dictionary is called an entry. Each entry consists of one object that represents the key and a second object that is that key’s value. Within a dictionary, the keys are unique. That is, no two keys in a single dictionary are equal (as determined by isEqual:). In general, a key can be any object (provided that it conforms to the NSCopying protocol—see below), but note that when using key-value coding the key must be a string (see “Key-Value Coding Fundamentals”). Neither a key nor a value can be nil; if you need to represent a null value in a dictionary, you should use NSNull.
So any object that meets the NSCopying protocol can be used as a key. The string restriction is only for Key-Value Coding used for Cocoa bindings
I'm inferring that you are using a key that is does not conform to the NSCopying Protocol. In that case try using the Core Foundation equivalent of NSDictionary: CFDictionary.
http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFDictionaryRef/Reference/reference.html
Just make sure that when you are using CFDictionary that all of your objects are going to be retained in memory for the duration of the object. Since CFDictionary has to be set with weak references (at least in my experience) just be careful that you don't accidentally dealloc one of your objects whiles it's still in the CFDictionary.
While CFDictionary is “toll-free bridged” with its Cocoa Foundation counterpart, NSDictionary, I find that there are still problems with this. I've tried to add objects that were not part of the NSCopying protocol to my toll-free bridged NSDictionary and it came up with an error at run time.
CFDictionary docs: http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFDictionaryRef/Reference/reference.html
If you need mutability, use CFMutableDictionary instead like so:
cfd = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
CFDictionaryAddValue(cfd, key, value);
CFRelease(cfd);
CFMutableDictionary docs: http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFMutableDictionaryRef/Reference/reference.html#//apple_ref/doc/uid/20001497

NSMutableDictionary. Using an object to store an integer value

I'd like to add instances of my custom class to a NSMutableDictionary so that each instance has a corresponding integer value? I later want to be able to retrieve the integer value using the class instance as the key.
NS*Dictionary requires objects for both keys and values. If you want to shove an integer into a collection instance (dictionary, array, set, etc.) then you must "box" it first using NSNumber; [NSNumber numberWithInt:x];.
The keys to an NSMutableDictionary generally need to be copyable and must have a stable hash and stable isEqual: behavior (as per the documentation). Thus, your instances of your custom class must fulfill the NSCopying protocol and must properly support hash and isEqual:. hash and isEqual: may likely "just work" if pointer equality is good enough. Copying can be tricky.
In general, though, it is rare to have a map between instances and integral values. Why not just add an #property to your class?
i.e.
#property int magicNumber;
Far more straightforward and a heck of a lot more efficient, too.
Note, also, that if you need to associate something with an existing instance where you can't modify the class, you should use objc_setAssociatedObject() and objc_getAssociatedObject(). They don't require that the instance be NSCopyingable and are relatively efficient, too (though not as efficient as an #property).
You should be able to simply use [myMutableDict setObject: [NSNumber numberWithInt: myInt] forKey: myClassInstance];
At least thats what i read out of the fact that setObject takes (id) as parametertypes. :)

How to test property existence and type based on NSString typed key?

In my quest to update a Core Data model within my iOS project, I'm querying a server for JSON objects that correspond - to some extent - with the managed entities of my model. The end result I'm striving for is a reliable update solution from JSON output.
For the examples in this question, I'll name the core data managed object existingObj and the incoming JSON deserialized dictionary updateDict. The tricky part is dealing with these facts:
Not all properties of the existingObj are present in the updateDict
Not all properties of the updateDict are available in the extistingObj.
Not all types of existingObj's properties match the JSON deserialized properties. (some strings may need a custom Objective-C wrapper).
updateDict may contain values for keys that are uninitialized (nil) in existingObj.
This means that while iterating through the updated dictionaries, there has to be some testing of properties back and forth. First I have to test whether the properties of the updateDict exist in existingObj, then I set the value using KVC, like so:
// key is an NSString, e.g. #"displayName"
if ([existingObj respondsToSelector:NSSelectorFromString(key)) {
[existingObj setValue:[updateDict objectForKey:key] forKey:key];
}
Although this part works, I don't like the fact that I'm actually testing for displayName as a getter, while I'm about to call the setDisplayName: setter (indirectly via KVC). What I'd rather to is something like [existingObj hasWritablePropertyWithName:key], but something that does this I can't find.
This makes for subquestion A: How does one test for a property setter, if you only have the property's name?
The next part is where I'd like to automate the property identification based on their types. If both the updateDict and the existingObj have an NSString for key #"displayName", setting the new value is easy. However, if the updateDict contains an NSString for key #"color" that is #"niceShadeOfGreen", I'd like to transform this into the right UIColor instance. But how do I test the type of the receiving property in existingObj so I know when to convert values and when to simply assign? I was hoping for something along the lines of typeOfSelector:
if ([existingObj typeOfSelector:sel] == [[updateDict objectForKey:key] class]) {
// regular assignment
} else {
// perform custom assignment
}
Of course this is boguscode. I can't rely on testing the type of the existingObj-property's value, for it may be unitialized or nil.
Subquestion B: How does one test for the type of a property, if you only have the property's name?
I guess that's it. I figured this must be a dupe of something that's already on here, but I couldn't find it. Maybe you guys can?
Cheers, EP.
P.S. If you'd have a better way to synchronize custom Objective-C objects to deserialized JSON objects, please do share! In the end, the result is what counts.
If you want to query whether an object has a setter for a given KVC key called key which corresponds to a declared property, you need to check whether it responds to a selector method called setKey: (starts with set, capitalise the first character in key, add a trailing colon). For instance,
NSString *key = #"displayName";
NSString *setterStr = [NSString stringWithFormat:#"set%#%#:",
[[key substringToIndex:1] capitalizedString],
[key substringFromIndex:1]];
if ([obj respondsToSelector:NSSelectorFromString(setterStr)]) {
NSLog(#"found the setter!");
[obj setValue:someValue forKey:key];
}
Two remarks:
Even though properties can have setters with names that do not follow the pattern described above, they wouldn’t be KVC compliant, so it is safe to check for set<Key>: since you’re using KVC to set the corresponding value.
KVC doesn’t use the setter method only. If it doesn’t find a setter method, it checks whether the class allows direct access to instance variables and, if so, use the instance variable to set the value. Also, if no setter method or instance variable is found, it sends -setValue:forUndefinedKey: to the receiver, whose class might have overridden the standard implementation that throws an exception. This is described in the Key-Value Coding Programming Guide.That said, if you’re always using properties, checking for the setter method should be safe.
As for your second question, it is not possible to query the runtime to know the actual Objective-C class of a property. From the runtime perspective, there’s an implementation specific type encoding for properties and general types (such as method parameters/return types). This type encoding uses a single encoding (namely #) for any Objective-C object, so the type encoding of an NSString property is the same as the type encoding of a UIColor property since they’re both Objective-C classes.
If you do need this functionality, one alternative is to process your classes and add a class method that returns a dictionary with keys and corresponding types for every property (or the ones you’re interested in) declared in that class and superclasses, or maybe some sort of description language. You’d have to do this on your own and rely on information not available during runtime.