A common tripup for programmers moving from other languages to Objective-C is dealing with the difference between equality and identity. This can be especially problematic because the rules for identity and equality are different depending on whether you are dealing with primitives or objects, and because FoundationKit can pull some tricks behind the scenes that can confuse the understanding of equality and identity.
A simple conceptual model can make the difference pretty easy to grasp.
Imagine you have a pair of one dollar bills. Both bills are “equal”, because you could use either bill to pay for a one dollar item.
They are however not identical. Each bill has a unique serial number that indicates when and where it was made. No two dollar bills have the same serial number. If you told me the serial number of one of the bills, you could put in in a pile with thousands of other bills and I would still be able to uniquely identify that particular bill.
Now imagine the same test with coins instead of dollar bills. While coins do have some slight variation (the year and pressing facility may vary), I can easily aquire a bag of 1000 quarters, all of which are not only equal but identical. There is no way to identity a particular quarter once it has been thrown in a pile of identical quarters.
Objects (NSObject, NSView, etc) are like dollar bills, each one is unique but may be equal to others. Primitives (int,float,char) are like coins, if they are equal they are identical.
With this in mind, I’ll now take you through a more long winded version….
Reading the Syntax
Now take a step back in time to before you were a programmer to a time when you are learning basic math. You are presented with this mathematical phrase and this way of reading the phrase:
| Expression | Reads As: |
| a=b | a equals b |
Once you start programming in a C language the “reading” of that changes, because the equal sign becomes an assignment operator, and the dual equal sign becomes the comparison for equality.
| Expression | Reads As: |
| a=b | a is assigned the value of b |
| a==b | a is equal to b |
This works fine for C where the only types of variables are primitives like int, float, and char. But in Objective-C there is both identity and equality.
| Expression | Reads As: |
| a=b | a is assigned the value of b |
| a==b | a is identical to b |
| [a isEqual:b] | a is equal to b |
When it comes to objects:
- if
(a==b)is true then it must follow that([a isEqual:b])is also true. - However the opposite is not necessarily true. If
([a isEqual:b])is true, it does not mean that(a==b), although it may be the case even when you don’t expect it to be.
Source Code Based Examples
You can download all the source code for these examples from svn://svn.karlkraft.com/equality
equality_01.m – primitive identity
This first example uses primitives. Sure enough, when compiled it outputs a single like that shows that both variables are equal.
//
// equality_01.m
// Equality
//
// Created by Karl Kraft on 7/12/07.
// Copyright 2007 Karl Kraft. All rights reserved.
//
int main(int argc, char **argv) {
int a=4;
int b=4;
if (a==b) {
NSLog(@"a is equal and identical to b");
}
return 0;
}
equality_02.m – Object equality and identity
The second example uses objects:
// // equality_02.m // Equality // // Created by Karl Kraft on 7/12/07. // Copyright 2007 Karl Kraft. All rights reserved. // #importint main(int argc, char **argv) { NSString *a=[NSString stringWithFormat:@"This is a string"]; NSString *b=[NSString stringWithFormat:@"This is a string"]; NSString *c=[NSString stringWithFormat:@"This is a longer string"]; if (a==b) { NSLog(@"a is identical to b"); } if (a==c) { NSLog(@"a is identical to c"); } if ([a isEqual:b]) { NSLog(@"a is equal to b"); } if ([a isEqual:c]) { NSLog(@"a is equal to c"); } return 0; }
When this example is run, only a single line is output, since a, b, and c are not identical at all, even though a and b have the same value.
2007-07-12 08:08:21.030 equality_02[8262:813] a is equal to b
equality_03.m: Sometimes two different objects are really the same object and therefore identical.
Example #2 was carefully crafted. A slight change in example #3 produces results you may not expect. In this example we switch from using the method stringWithFormat: to using the method stringWithString:
// // equality_03.m // Equality // // Created by Karl Kraft on 7/12/07. // Copyright 2007 Karl Kraft. All rights reserved. // #importint main(int argc, char **argv) { NSString *a=[NSString stringWithString:@"This is a string"]; NSString *b=[NSString stringWithString:@"This is a string"]; NSString *c=[NSString stringWithString:@"This is a longer string"]; if (a==b) { NSLog(@"a is identical to b"); } if (a==c) { NSLog(@"a is identical to c"); } if ([a isEqual:b]) { NSLog(@"a is equal to b"); } if ([a isEqual:c]) { NSLog(@"a is equal to c"); } return 0; }
This small change results in a program with different output, even though you would seem to think that a is equal, but not identical to b.
2007-07-12 08:19:52.994 equality_03[8529:813] a is identical to b 2007-07-12 08:19:52.995 equality_03[8529:813] a is equal to b
In some particular cases identity comparisons for non mutable objects like NSString will return true because of an optimization performed by the compiler and by the Cocoa classes. With this disparity between the second and third example, it is easy to see how a new programmer could get confused if they write a simple test application to compare two strings with the identity operator to test for equality, and find that is works.
The first cause of this exception is the compiler/linker part of the tool chain. To reduce memory usage if it finds two strings that are equal, it will only place one copy in the final binary, and have both variables point to the single instance of the string.
The second is the way that non mutable instances of classes like NSArray, NSData, NSDictionary, and NSString deal with making copies of strings.
| If a non-mutable NSString is created as a copy of another non-mutable NSString, instead of allocating memory and copying the value, the original string will be returned. A similar rule applies for other classes like NSArrary, NSDictionary, and NSData that have both mutable and non-mutable variants. If either the created or source object is mutable this rule does not apply. |
equality_04.m: mutable and immutable copying
// // equality_04.m // Equality // // Created by Karl Kraft on 7/12/07. // Copyright 2007 Karl Kraft. All rights reserved. // #importint main(int argc, char **argv) { NSString *a=[NSString stringWithString:@"This is a string"]; NSString *b=[NSString stringWithString:@"This is a string"]; NSMutableString *c=[NSMutableString stringWithString:@"This is a string"]; NSString *d=[NSString stringWithString:c]; if (a==b) { NSLog(@"a is identical to b"); } if (a==c) { NSLog(@"a is identical to c"); } if (a==d) { NSLog(@"a is identical to d"); } if ([a isEqual:b]) { NSLog(@"a is equal to b"); } if ([a isEqual:c]) { NSLog(@"a is equal to c"); } if ([a isEqual:d]) { NSLog(@"a is equal to d"); } return 0; } 2007-07-12 08:53:34.872 equality_04[8891:813] a is identical to b 2007-07-12 08:53:34.874 equality_04[8891:813] a is equal to b 2007-07-12 08:53:34.876 equality_04[8891:813] a is equal to c 2007-07-12 08:53:34.878 equality_04[8891:813] a is equal to d
Symbolically, the order of events in this program is something like this. During the compile we have three embedded copies of the string @”This is a string”

The linker then reduces these to a single instance of the string.
When the program runs, NSString is asked to create a new string from that embedded constant, and this is assigned to the variable a. Rather than create a new string, a just becomes a pointer to the original string.

The same step is repeated with the variable b.

When we get to the next like, c is made a mutable version of the string. Since mutable versions can change over time, a new instance of the string is created, and c points to this instance.

Finally, d becomes a copy of c. Even though d is immutable, it is possible that the string c has been changed since the creation of the mutable version of the non-mutable shared instance. So d becomes yet another instance of the string:

Thus, a,b,c, and d are all equal, but only a and b are identical.
Keep in mind that you should not rely on this behavior as it is particular to the implementation of NSString and various linker flags that may be turned on or off.
That a and b are identical is the result of optimization. It can change depending on compiler flags, whether a and b are in the same library or not, and on the implementation of NSString. In future versions, NSMutableString may be smart enough to realize that is it an unmodified version of a non-mutable string, and therefore non-mutable copies of the mutable string would be identical to the original non-mutable string, and in that case a,b, and d would be identical.
Always test for equality instead of identity unless:
|
example_05 – Sometimes equal objects aren’t equal unless they are identical
Example #5 has a main function that creates two instance of MyCircle and comapres them for equality. The comparison returns false even though it would seem that it should return true since the only state of the circles, the raidus, is identical in both.
// // main.m // Equality Example #5 // // Created by Karl Kraft on 7/12/07. // Copyright 2007 Karl Kraft. All rights reserved. // #import#import "MyCircle.h" int main(int argc, char **argv) { MyCircle *a = [MyCircle circleWithRadius:10]; MyCircle *b = [MyCircle circleWithRadius:10]; if ([a isEqual:b]) { NSLog(@"a is equal to b"); } else { NSLog(@"a is not equal to b"); } return 0; } // // MyCircle.m // Equality Example #5 // // Created by Karl Kraft on 7/12/07. // Copyright 2007 Karl Kraft. All rights reserved. // #import "MyCircle.h" @implementation MyCircle - initWithRadius:(int)anInt; { [self init]; radius=anInt; return self; } +(MyCircle *)circleWithRadius:(int)anInt; { MyCircle *aCircle=[[self alloc] initWithRadius:anInt]; return aCircle; } - (int)radius; { return radius; } @end
So why is a not equal to b?
Because isEqual: method need to be written for each class. Since MyCircle doesn’t implement isEqual, the superclass implementation in NSObject is used. That implementation just return true if the objects are identical.
| If your class is a direct subclass of NSObject and you don’t implement isEqual, then the test for equality is a test of identity |
The reason for this behavior is that the author of NSObject isn’t really capable of determining whether any instance of a subclass you write in the future is truly equal to another instance. Your two instances may have some state that is different yet doesn’t affect the equality. For instance two arrays with the same objects but different capacities may be considered equal. A mutable string and a non-mutable string are two very different classes but they are considered equal if the underlying string is the same.
If you write a class that is going to need to be compared for equality then you should write an isEqual method. And in most cases any subclass of NSObject will need an isEqual method.
| Even though you may not be directly comparing two instances of your class, other classes will. For example NSArrary when determining equality to a second NSArray asks each instance whether it is equal to the instance in the other NSArray. Another example is NSSet which uses equality to determine whether you are allready in the set. |
Along with implementing isEqual you should implement a hash method as well. Many kit classes will use the hash method to determine whether it is possible for two instances to be equal.
| Instances that are equal must have equal hash values. |
Example 6 shows an implementation of MyCircle.m that will work correctly.
//
// MyCircle.m
// Equality
//
// Created by Karl Kraft on 7/12/07.
// Copyright 2007 __MyCompanyName__. All rights reserved.
//
....
- (BOOL)isEqual:(id)otherObject;
{
if ([otherObject isKindOfClass:[MyCircle class]]) {
MyCircle *otherCircle= (MyCircle *)otherObject;
if (radius != [otherCircle radius]) return NO;
return YES;
}
return NO;
}
- (NSUInteger) hash;
{
return radius;
}
@end
There are a couple of important points to keep in mind when implementing isEqual and hash.
- Two instances that are equal must return the same hash.
- The hash method should be quick since it may be called often if your class is stored in an NSSet or NSHashTable.
- If you have more than a single piece of state, use the XOR operator to create the hash. For example if your MyCircle class had an x and y location as well you could use:
- (NSUInteger) hash; { return radius ^ xLocation ^ yLocation; } - If your have other objects as instance variables, use their hash to build your hash. If you had a color and string associated with your MyCircle class you might use:
- (NSUInteger) hash; { return radius ^ xLocation ^ yLocation ^ [myColor hash] ^ [myString hash]; } - Keep in mind that the method for equality is called “isEqual” and not “isEqualTo”. A category of NSObject related to scripting declares a method called isEqualTo: which is a cover for isEqual. But isEqualTo: is only called by the scripting engine.
- You can’t assume that the object passed to your isEqual: method will be another instance of your class, or even a subclass of your class. So perform a test for class membership first thing in your method and then perform a cast once you are sure you are dealing with an instance of your class or subclass.
- Keep in mind that subclasses may end up calling [super isEqual:] to perform part of the equality test, so don’t write an implementation of isEqual: that assume both self and otherObject are of the exact same class. This is why you should use isKindOfClass instead of isMemberOfClass.
- Conversely, you may not want to call [super isEqual:] as part of your equality implementation, since you may be getting the NSObject implementation of identity instead of equality. Always check the documentation and the implementors list, if you are subclassing some class other than NSObject and implementing isEqual:
