How does one compare objects in Objective-C?
Is it as simple as == ?
I want to check an array for an object and if it doesnt exist add it to the array otherwise, remove it from the array.
Comparing objects in Objective-C works much the same as in Java or other object-oriented languages:
== compares the object reference; in Objective-C, whether they occupy the same memory address.
isEqual:, a method defined on NSObject, checks whether two objects are "the same." You can override this method to provide your own equality checking for your objects.
So generally to do what you want, you would do:
if(![myArray containsObject:anObject]) {
[myArray addObject:anObject];
}
This works because the Objective-C array type, NSArray, has a method called containsObject: which sends the isEqual: message to every object it contains with your object as the argument. It does not use == unless the implementation of isEqual: relies on ==.
If you're working entirely with objects that you implement, remember you can override isEqual: to provide your own equality checking. Usually this is done by comparing fields of your objects.
Every Objective-C object has a method called isEqual:.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/isEqual:
So you would want to override this for your custom object types.
One particular important note in the documentation:
If two objects are equal, they must
have the same hash value. This last
point is particularly important if you
define isEqual: in a subclass and
intend to put instances of that
subclass into a collection. Make sure
you also define hash in your subclass.
== will compare the pointer, you need to override
- (BOOL)isEqual:(id)anObject
Implement isEqual: and hash
Per the Apple documentation on NSObject you need to implement isEqual: and hash at a minimum. Below you'll find one way to implement object equality, of course how to implement hash enters the land of serious debate here on StackOverflow, but this will work. General rule - you need to define what constitutes object equality and for each unique object they should have a unique hash. It is best practice to add an object specific equality method as well, for example NSString has isEqualToString:.
- (BOOL)isEqual:(id)object
{
BOOL result = NO;
if ([object isKindOfClass:[self class]]) {
CLPObject *otherObject = object;
result = [self.name isEqualToString:[otherObject name]] &&
[self.shortName isEqualToString:[otherObject shortName]] &&
[self.identifier isEqualToString:[otherObject identifier]] &&
self.boardingAllowed == [otherObject isBoardingAllowed];
}
return result;
}
- (NSUInteger)hash
{
NSUInteger result = 1;
NSUInteger prime = 31;
result = prime * result + [_name hash];
result = prime * result + [_shortName hash];
result = prime * result + [_identifier hash];
result = prime * result + _boardingAllowed;
return result;
}
Related
I'm trying to write an instance method for a Card class that compares a single card against an array. The class has some properties like: shape and color. The otherCards array is filled with other instances of this class, that also have their shapes and colors.
Now, I want to write a method that can check all of these attributes separately. How can I pass in a particular attribute, as in: [allAttributesIsEqual:otherCards compareWith: self.shape]? So I can pass in self.shape or self.color when actually comparing?
- (BOOL)allAttributesIsEqual: (NSArray *)otherCards
{
//self.shape is equal to othercards.shape
}
You can't just pass in self.shape, because that will give you the value of the property. Thanks to some of Cocoa/ObjC's dynamite, however, you can pass in the name of a property (or method) and get the results later.
The clever (dare I say, perhaps even "Pythonic") way:
// The name of the property we're interested in.
NSString * key = #"color";
// Get the values of that property for all the Cards in the array, then
// collapse duplicates, because they'll give the same results when comparing
// with the single card.
NSSet * vals = [NSSet setWithArray:[arrayOfCards valueForKey:key]];
// Now, if the set has only one member, and this member is the same
// as the appropriate value of the card we already have, all objects
// in the array have the same value for the property we're looking at.
BOOL colorIsEqual = ([vals count] == 1 && [vals containsObject:[myCard valueForKey:key]]);
Then your method can look like this:
- (BOOL)allOtherCards: (NSArray *)otherCards haveEqualAttribute: (NSString *)key;
Dan F's suggestion to implement - (BOOL)<#property#>Equal: (NSArray *)otherCards; for each property you're interested in is not at all a bad idea, however. Of course, each of these could call through to the base "clever" version.
The idea is that you (as the Card class) know what it means for two instances to be "equal". It sounds like in your case, two Cards are equivalent if their color and shape properties match. Start by implementing -isEqual: (along with -hash) on your custom Card class. This is the standard way of having an object expose a notion of whether it is the same as some other object. You can implement this however you need. Within this isEqual method, you can check all of the relevant properties:
- (BOOL)isEqual:(id)otherObject
{
if (![otherObject isKindOfClass:[self class]) {
return NO;
}
Card * otherCard = (Card *)otherObject;
// now compare the attributes that contribute to "equality"
return ([self.shape isEqual:otherCard.shape] && [self.color isEqual:otherCard.color]);
}
Now, once your custom object supports this -isEqual:, you can check all the cards in the array to see if any are equal to the candidate card. You could do the loop yourself and use the -isEqual:, but the nice thing about doing this in the system standard way is that you can also use system provided convenience methods to check for collection membership, like:
if ([myCardList containsObject:candidateCard]) {
// one of the cards compared as "equal"
}
If you would prefer to do this as you request in a method on your class, you could then structure it like so:
- (BOOL)isRepresentedInArray:(NSArray *)arr
{
return [arr containsObject:self];
}
In objective-C I find myself creating alot of Mutable objects and then returning them as non mutable objects. Is the way I am doing it here, simply returning the NSMutableSet as an NSSet a good practice? I was thinking maybe I should specify that i make a copy of it.
/** Returns all the names of the variables used in a given
* program. If non are used it returns nil */
+ (NSSet *)variablesUsedInProgram:(id)program
{
NSMutableSet* variablesUsed = [[NSMutableSet alloc]init];
if ([program isKindOfClass:[NSArray class]]) {
for (NSString *str in program)
{
if ([str isEqual:#"x"] || [str isEqual:#"y"] || [str isEqual:#"a"] || [str isEqual:#"b"])
[variablesUsed addObject:str];
}
}
if ([variablesUsed count] > 0) {
return variablesUsed;
} else {
return nil;
}
}
If I were you, I would do it this way.
+ (NSSet *)variablesUsedInProgram:(id)program
{
NSSet *variablesUsed;
if ([program isKindOfClass:[NSArray class]]) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF = 'x' or SELF = 'y' or SELF = 'z'"];
variablesUsed = [NSSet setWithArray:[program filteredArrayUsingPredicate:predicate]];
}
int count;
return (count = [variablesUsed count]) > 0 ? variablesUsed : nil;
}
I find using predicate to filter array quite comprehensive and easy. Rather than dealing with creating a new mutable type and then testing certain condition, adding until the loop; in this scenario, it seems to be easier to use predicate. Hope this helps you.
It depends how much safety you require. If you return the object as an NSSet it will still be an NSMutableSet, so it could easily be cast back to one and modified.
Certainly, if you're creating a public API, I'd recommend returning a copy. For in internal project, perhaps the method signature already makes the intention clear enough.
Its, worth noting that, generally the performance impact of returning a copy is negligible - copying an immutable instance is effectively free whereas each copy sent to a mutable-passing-as-immutable will create another copy. So I would say its good practice to default to.
No. This is an absolutely correct OOP approach (it takes advantage of polymorphism). Every NSMutableSet is a proper NSSet. Don't copy superfluously.
Not a full answer here, consider NSProxy's one, but I want to clarify something.
In your case you create your object from scratch, and you don't set any ivar to point to that object. In my opinion in a good percentage of cases you don't need to make a copy of the mutable object returned. But if there is a good reason to deny the class client from mutating the class, then you should copy the variable.
Consider a property like this:
#property (nonatomic,assign) NSSet* set;
The class client could do this:
NSMutableSet* set= ... ; // inizialized to some value
classInstance.set= set;
// Mutate the set
Once mutated the set it could make the class be in an inconsistent state.
That's why when I have a property with the type of a class that has also a mutable version, I always put copy instead of assign in the property.
I'm trying to write an instance method for a Card class that compares a single card against an array. The class has some properties like: shape and color. The otherCards array is filled with other instances of this class, that also have their shapes and colors.
Now, I want to write a method that can check all of these attributes separately. How can I pass in a particular attribute, as in: [allAttributesIsEqual:otherCards compareWith: self.shape]? So I can pass in self.shape or self.color when actually comparing?
- (BOOL)allAttributesIsEqual: (NSArray *)otherCards
{
//self.shape is equal to othercards.shape
}
You can't just pass in self.shape, because that will give you the value of the property. Thanks to some of Cocoa/ObjC's dynamite, however, you can pass in the name of a property (or method) and get the results later.
The clever (dare I say, perhaps even "Pythonic") way:
// The name of the property we're interested in.
NSString * key = #"color";
// Get the values of that property for all the Cards in the array, then
// collapse duplicates, because they'll give the same results when comparing
// with the single card.
NSSet * vals = [NSSet setWithArray:[arrayOfCards valueForKey:key]];
// Now, if the set has only one member, and this member is the same
// as the appropriate value of the card we already have, all objects
// in the array have the same value for the property we're looking at.
BOOL colorIsEqual = ([vals count] == 1 && [vals containsObject:[myCard valueForKey:key]]);
Then your method can look like this:
- (BOOL)allOtherCards: (NSArray *)otherCards haveEqualAttribute: (NSString *)key;
Dan F's suggestion to implement - (BOOL)<#property#>Equal: (NSArray *)otherCards; for each property you're interested in is not at all a bad idea, however. Of course, each of these could call through to the base "clever" version.
The idea is that you (as the Card class) know what it means for two instances to be "equal". It sounds like in your case, two Cards are equivalent if their color and shape properties match. Start by implementing -isEqual: (along with -hash) on your custom Card class. This is the standard way of having an object expose a notion of whether it is the same as some other object. You can implement this however you need. Within this isEqual method, you can check all of the relevant properties:
- (BOOL)isEqual:(id)otherObject
{
if (![otherObject isKindOfClass:[self class]) {
return NO;
}
Card * otherCard = (Card *)otherObject;
// now compare the attributes that contribute to "equality"
return ([self.shape isEqual:otherCard.shape] && [self.color isEqual:otherCard.color]);
}
Now, once your custom object supports this -isEqual:, you can check all the cards in the array to see if any are equal to the candidate card. You could do the loop yourself and use the -isEqual:, but the nice thing about doing this in the system standard way is that you can also use system provided convenience methods to check for collection membership, like:
if ([myCardList containsObject:candidateCard]) {
// one of the cards compared as "equal"
}
If you would prefer to do this as you request in a method on your class, you could then structure it like so:
- (BOOL)isRepresentedInArray:(NSArray *)arr
{
return [arr containsObject:self];
}
I’m implementing -isEqual: for my custom class. The equality is based on the property values, ie. if all properties are equal, the objects are considered equal. Together with the traditional class check the code looks like this:
- (BOOL) isEqual: (id) object
{
return [object class] == [self class]
&& [[object someProperty] isEqual:someProperty]
&& [[object otherProperty] isEqual:otherProperty];
}
But this fails for nil property values, ie. two objects of the class having nil values stored in someProperty are considered non-equal, whereas I would like them to be equal. Thus I arrived at the following version:
- (BOOL) isEqual: (id) object
{
#define equals(a, b) ((a == b) || ([a isEqual:b]))
return equals([object class], [self class])
&& equals([object someProperty], someProperty)
&& equals([object otherProperty], otherProperty);
}
This seems to work fine. Is this the “standard” way to solve the equality? Seems overly complex to me.
isEqual: is very much object-specific. It's a semantic equality. It is up to you to define for every class what isEqual means. Thus there is no standard way to do it.
The simplest implementation is return self == object, your second implementation is very generic and nice, but not necessarily well suited to every class. Per example, for a Person, comparing the emails could be sufficient, or the emails and first names if you suppose an email could be used by several family members.
Ok a pretty simple question.. in c++ it seems to work but in objective-c i seem to struggle with it :S ..
If you want to compare two arrays it should be something like this right
for ( int i = 0; i < [appdelegate.nicearray count]; i++ )
{
if ( appdelegate.nicearray[i] == appdelegate.exercarray[i] )
{
NSLog(#"the same elements in this selection");
}
}
what's the problem exactly ?
These are Cocoa array objects (instances of NSArray), not C arrays or C++ vectors, and remember that Objective-C does not have operator overloading. The only things you can do with an object are pass it around, store it in variables, and send messages to it.
So the array-subscript operator is wrong with Objective-C objects. I don't think it's even linguistically valid to dereference a pointer to an Objective-C object, so this code should be giving you a compiler error. I may be misremembering, though. If it does make it to runtime, that code will crash sooner or later, since you're accessing memory beyond the ends of the array objects.
(EDIT from the year 2013: Objective-C now supports subscripting of objects. This ultimately translates into the appropriate objectAtIndex: or replaceObjectAtIndex:withObject: message. So, the code in the question would actually work now, although it's still not the proper way to simply walk an array, much less to compare two arrays.)
The proper way to retrieve an object from an NSArray object by its index is not to use the array-subscript operator, but to send the array object the objectAtIndex: message:
[myArray objectAtIndex:i]
The proper way to iterate on the elements of an array object, assuming you don't really need the index for something else (such as replacing objects in a mutable array), is to loop on it directly (this is called “fast enumeration”):
for (MyObject *myObject in myArray) {
…
}
NSArray also responds to objectEnumerator and reverseObjectEnumerator, which return a similarly-iterable object. Of the two, reverseObjectEnumerator is the more useful in new code, since you can just iterate on the array directly to iterate forward. Both of them were most useful before fast enumeration existed; that code looked like this:
NSEnumerator *myArrayEnum = [myArray objectEnumerator];
MyObject *myObject;
while ((myObject = [myArrayEnum nextObject])) {
…
}
(Yes, that's an assignment in the condition. Deliberately, hence the extra (). We coded boldly back then, didn't we?)
For what you're doing, though, you more likely want to send one of the arrays an isEqualToArray: message, as Williham Totland suggested:
BOOL theyAreEqual = [myFirstArray isEqualToArray:mySecondArray];
This will make sure both arrays have the same length, then walk them both in lock-step, sending isEqual: to each pair of objects. It'll return YES if every isEqual: message returned YES; NO otherwise. The arrays may contain different objects, but as long as each pair is equal, the arrays themselves are equal.
That assumes you want object equality. Two separate objects are equal if one of them responds with YES when you send it an isEqual: message and pass the other object. If you meant to compare the identities of the objects, then you do need to do the lock-step loop yourself and use ==:
BOOL arraysContainTheSameObjects = YES;
NSEnumerator *otherEnum = [otherArray objectEnumerator];
for (MyObject *myObject in myArray) {
if (myObject != [otherEnum nextObject]) {
//We have found a pair of two different objects.
arraysContainTheSameObjects = NO;
break;
}
}
But that's unlikely. Most of the time, I have wanted to test the objects' equality, not identities, so isEqualToArray: is what I wanted.
You want the isEqualToArray: method. As in:
if ([arrayOne isEqualToArray:arrayTwo]) {
// Do something
}
This will recursively compare the two arrays, while having the advantage of not being needlessly circuitous and not requiring a loop.
Try telling us the result you're getting when you run this code. The approach is correct, but try this one:
for (int i =0; i< appdelegate.nicearray.count; i++)
{
if ([[appdelegate objectAtIndex:i] isEqual: [appdelegate.exercarray objectAtIndex:i]])
{
NSLog(#"the same");
}
}
Here's a little one I put together based on the top ranked example. This merely checks that the arrays contains the same values, irrespective of order and if there are any duplicates. I mainly use this to compare keys of two dictionaries (which often return their allKeys arrays in various sort orders) to see if they contain the same objects. Thanks Peter Hosley for providing the example I adapted this from.
#pragma mark - Arrays
// Check to see if arrays contain the same elements, not necessarily in the same order
// This is different from [array isEqualToArray:responseKeys] which demands the same order in both arrays
// ## Does not compensate for duplicate entries in an array
+ (BOOL)doArraysContainTheSameObjects:(NSArray *)firstArray withArray:(NSArray *)secondArray {
BOOL arraysContainTheSameObjects = YES;
for (id myObject in firstArray) {
if (![secondArray containsObject:myObject]) {
// We have found an object that is not in the other array.
arraysContainTheSameObjects = NO;
break;
}
}
return arraysContainTheSameObjects;
}
I do the following when comparing arrays:
Check to see if any of the arrays are nil when the other is not
Check to see if the lengths are the same
Iterate (using a for loop like you have) over each element checking the matching element in the other array.
To compare elements you need to define what you want to regard as being "equal". Are they equal only if the pointers in the array are equal or can they be equal if the content is equal too.
For the pointer case, you can use ==.
For the deep comparison you might need to use CompareTo or something similar.