When calling NSSet's setByAddingX methods, which set's objects win a tie? - iphone

I bring this up because objects that compare the same with isEquals: aren't necessarily identical. Some (many) objects only compare certain properties to determine equality.
That makes the exact behavior of the following NSSet methods important:
setByAddingObject:
setByAddingObjectsFromArray:
setByAddingObjectsFromSet:
The documentation does not specify what happens when the receiver and the parameter contain equivalent objects. Will the resulting NSSet contain the object from the receiver, or the object from the "other" parameter?
Note that in NSMutableSet, it DOES specify the behavior of its add methods--objects will NOT be added if an "equal" object already exists in the set.

The documentation of NSMutableSet's addObject: method used to cover a similar case:
If anObject is already present in the set, this method has no effect on either the set or anObject.
But, as you can see from following the link, the current version doesn't even say that. And even that statement really only covers trying to add the same object; it does not specifically address adding a different but equal object.
Relying on observed but not documented behavior is dangerous, not just because it can change between OS versions, but also because it can change within the very same process. That's because NSSet is a class cluster, meaning there may be multiple implementations. Which one you get depends on how you create the set; there is no way to ensure that a specific implementation will be chosen or even exist.*
That's because it shouldn't matter. Every one of the clustered subclasses presents the same behavior as defined in the cluster's interface and documentation. (If it ever doesn't, that's a bug and you should report it.) Given that all the subclasses do effectively the same things, it shouldn't matter which one you get an instance of.
The same principle applies to your objects. They're equal! For that reason, it shouldn't matter which one is in the set. If it does matter, then they are not truly equal, and you need to make the objects' definition of equality more rigid. (Don't forget to update both isEqual: and hash.)
Depending on what you're using the set for, you may want to take that even farther and ensure that no two equal objects can exist. To do this, move the ownership, maintenance, and use of the set into the member objects' class, and have it always return a matching object instead of creating a new one whenever possible and appropriate.
*And even if you could choose one of the implementations, there's no guarantee that it'd have the behavior you observed forever—it could, and Murphy says probably will, be different in another OS version.

I tested this with the following code. SomeClass is defined such that propertyA is the only property considered in hash and isEquals:
SomeClass *objectA = [[[SomeClass alloc] init] autorelease];
objectA.propertyA = #"test";
objectA.propertyB = #"objectA";
SomeClass *objectB = [[[SomeClass alloc] init] autorelease];
objectB.propertyA = #"test";
objectB.propertyB = #"objectB";
NSSet *setA = [NSSet setWithObject:objectA];
NSSet *setB = [NSSet setWithObject:objectB];
NSSet *setC = [setA setByAddingObjectsFromSet:setB];
NSLog(#"Set A\n%#", [setA description]);
NSLog(#"Set B\n%#", [setB description]);
NSLog(#"Set C\n%#", [setC description]);
The output when running this code is:
2011-03-03 16:35:15.041 temp[50311:207] Set A
{(
{SomeClass propertyA:test propertyB:objectA}
)}
2011-03-03 16:35:15.041 temp[50311:207] Set B
{(
{SomeClass propertyA:test propertyB:objectB}
)}
2011-03-03 16:35:15.042 temp[50311:207] Set C
{(
{SomeClass propertyA:test propertyB:objectA}
)}
This demonstrates that the newly created NSSet will contain objects from the RECEIVER in the case that the parameter contains equivalent objects.
EDIT - I'm marking this as the answer, because it directly answers the question at hand. I would however, point out Peter's answer below and the concerns he voices. This behavior is undocumented and as such, while it's extremely unlikely these core classes will change in this regard, it's worth pointing out that risk. If you write code assuming this behavior it is possible that it will break in a future release. Caveat emptor.

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

adding objects to Mutable array

I want to add items to mutable array from a dictionary. Problem is I want to check existing array items before adding new item. If same item is already there in the array, I want to replace it. else add the new item.
How could I do it?
You could perhaps use an NSMutableSet rather than an NSMutableArray. The addObject method on NSMutableSet will only "add a given object to the set, if it is not already a member."
If you'd like to check membership before adding to the set anyway, you can check the result of:
[mySet containsObject:myObjectFromDictionary]
...which returns a simple BOOL value indicating whether the set already contains an object whose isEqual method returns true when your object is passed to it.
(For a little extra functionality, NSCountedSet will keep track of the number of objects added to the "set" for which isEqual: returns true)
You could compare the result of : [yourArray indexOfObject:yourObject]; against NSNotFound to know if the object is in the array.
It will give you the index of the object to replace, or if it is equal to NSNotFound, you will add it.
Objects equality is tested with isEqual: method.
NSArray class reference.
On the face of it, both Vincent's and Rich's answers are correct.
However, there is a conceptual issue in the original question that hasn't been addressed.
Namely, that "membership in an array" via indexOfObject: (or containsObject: in a set) is ultimately done by comparing the two objects using isEqual:.
If isEqual: returns YES, then the two objects better had damned well be functionally identical in your code or else you have other, significantly more serious, problems in your design and implementation.
Thus, the real question should be "How do I detect if an object is already in an array and not add it?" and Rich's and Vincent's answer are both still correct.
I.e. you should only need to check for presence and, if present, take no action.
(Note that there are esoteric situations where replacement is actually warranted, but they are both truly esoteric and not generally used within the context of a mutable collection)

Why do people always use reassignment for instance variables in Objective-C (namely iPhone)?

I always see example code where in the viewDidLoad method, instead of saying, for example
someInstanceVar = [[Classname alloc] init];
they always go
Classname *tempVar = [[Classname alloc] init];
someInstanceVar = tempVar;
[tempVar release];
Why is this? Isn't it the exact same thing, just longer?
The short answer: This pattern shows up all the time in iPhone code because it is considered the best way to create a new object and assign it to a member variable while still respecting all of the memory management rules and invoking the appropriate side effects (if any) while also avoiding the use of autorelease.
Details:
Your second example would create a zombie, since var is left holding a pointer to memory that has been released. A more likely usage case looks like this:
tempVar = [[Classname alloc] init];
self.propertyVar = tempVar;
[tempVar release];
Assuming that propertyVar is a declared as copy or retain property, this code hands off ownership of the new object to the class.
Update 1: The following code is equivalent, but not recommended* on iOS, which is probably why most iPhone programs use the first pattern instead.
self.propertyVar = [[[Classname alloc] init] autorelease];
* autorelease is discouraged on iOS because it can cause problems when overused. The easiest way to be sure you never overuse it is to never use it all, so you will quite often see iOS code that uses alloc/init and release, even when autorelease would be acceptable. This is a matter of coder preference.
Update 2: This pattern looks confusing at first because of the memory management that Cocoa performs automagically behind the scenes. The key to it all is the dot notation used to set the member variable. To help illustrate, consider that the following two lines of code are identical:
self.propertyVar = value;
[self setPropertyVar:value];
When you use the dot notation, Cocoa will invoke the property accessor for the indicated member variable. If that property has been defined as a copy or retain property (and that is the only way for this pattern to work without creating a zombie), then several very important things happen:
Whatever value was previously stored in propertyVar is released
The new value is retained or copied
Any side effects (KVC/KVO notifications, for example) are automatically handled

Should I verify objects inside Foundation API containers?

In languages like C++ and C# when you create a contain such as a std::vector or a C# list you explicitly declare the container type when you create it:
C++:
std::vector<MyObject>
C#:
List<MyObject> list = new List<MyObject>();
Looking at the code above, I know immediately that these containers can only contain objects of type MyObject and the compiler will complain if I try to add an object that isn't off this type.
Since Objective-C is a dynamic language, we don't have the privilege of the compiler warning us about this (because it is a perfectly valid but potentially dangerous thing to do):
Objective-C:
NSDictionary *dict = [[NSDictionary alloc]init];
[dict setValue:[[SomeClass alloc]init] forKey:#"someClass"];
[dict setValue:[[NSMutableString alloc]init] forKey:#"mutableString"];
BOOL classIsSomeClass = [[dict objectForKey:#"someClass"] isKindOfClass:[SomeClass class]];
Instead something like an NSDictionary or NSArray will store and accept objects of any type that inherits from NSObject. I find this in itself very flexible but I cannot really be sure of the object type in the container I can only really know at runtime whereas with c++ or c# I know this at compile time and just by looking at the code.
Should I be validating the contents of the containers when adding, using and removing objects for container classes (NSArray, NSSet, NSDictionary, etc) from Apple's Foundation Framework? Or is this okay in all circumstances and will verification hurt performance much?:
NSDictionary *dict = [[NSDictionary alloc]init];
[dict objectForKey:#"someKey"]; // return nil?
Objective-C's dynamic messaging is much more like dynamic languages such as Python or Ruby. In these languages, the standard paradigm is often known as "duck typing". In other words, if an object instance quacks like a duck (i.e. responds to the message you're sending), it's a duck. In Objective-C, methods can be added at run time by a number of mechanisms, outside of the object inheritance hierarchy. So, it's much more common to ask whether an instance responds to a particular selector:
if([obj respondsToSelector:#selector(myMethod)]) {
[obj myMethod];
}
than to ask whether obj belongs to a certain class' hierarchy.
For the most part, Objective-C developers don't do this check unless they're getting object instances from "unknown" modules. Instead, we rely heavily on compiler warnings (the Objective-C compiler will warn about sending a message to a type that it isn't sure can receive that message) and unit testing. In this case, unit test to confirm that the correct objects are going into the collection and that you get the expected types out of the collection would probably go a long way to allaying your fears.
It does seem to be the "Objective-C Way" to avoid checking the types of an object taken from a collection. It's of course debatable whether this is good, but I think it's part of a general theme of preferring to think about the messages an object responds to rather than the object itself.
An example of this is the various ...Value (e.g. stringValue, intValue, etc.) messages that many objects respond to. Also worth noting is the fact that the id type automatically suppresses any warnings of the so-and-so may not respond to the such-and-such message variety.
I would say the pattern in Objective-C is to only store objects of one type in a container - and pretty much always you are sure of what is going into a container. That's why very few people in practice actually take the time to check the contents of a collection. When I do want to verify something, I usually use isKindOfClass: and a properly typed object to hold an item from the collection.
If you are really concerned about typing for some reason it would be pretty easy to create a wrapper class that implemented typed versions of objectAtIndex: and other common NSArray methods - note I'm not talking about a subclass of NSArray or any other collection, just an object that had similar message names. That kind of thing can be a drop in for lots of uses and you could always add a fall through method to get to the backing collection. But I think it's more trouble than it is worth and moves away from gully embracing the language.
In practice over many, many applications I almost never see "wrong type of object in an array" come up as an issue.
Now for a method that accepts an argument of typeID, that I am a lot more likely to check the type of before use - because those methods tend to take in a much wider range of objects.

Couldn't I just pass an copied string to an Core Data property?

The docs say:
The default implementation does not
copy attribute values. If the
attribute value may be mutable and
implements the NSCopying protocol (as
is the case with NSString, for
example), you can copy the value in a
custom accessor to help preserve
encapsulation (for example, in the
case where an instance of
NSMutableString is passed as a value).
So instead of getting into trouble and inconvenience with overwriting accessors in my NSManagedObject subclass, couldn't I simply do something like this?
myManagedObject.firstName = [[firstNameMutableStr copy] autorelease];
This would have the exact same effect, or not? The dynamic implementation would retain that anyways ... so.... why not the easy way?
It's an open question whether having to remember to copy the mutable string every where in code you set the attribute is "the easy way."
With a custom accessor, you just write the copy once then forget about. It copies automatically from that point on.
Just imagine that in thousands of lines of code you forgot to copy just once and developed a subtle bug because that one attribute of the managed object sporadically changed because some other totally unrelated code subsequently changed the mutable string you held only by reference.
I could tell you some stories of weekends lost to debugging because someone took "the easy way."