8. Understand the concept of “object equality”

  • Comparing objects by equality is a very useful feature. use= =Operator comparisons do not necessarily yield correct results, because the operator compares the two Pointers themselves, not the objects to which they point. You should useNSObjectAs stated in the agreementisEqual:Method to judge the equality of two objects.
  • In general, two objects of different types are not equal.
  • Some objects provide their own method of equivalence determination, which you can use if you know that two objects belong to the same class.
NSString *foo = @"Badger 123";
NSString *bar = [NSString stringWithFormat:@"Badger %i", 123];
NSLog(@"%d", foo == bar);		// false
NSLog(@"%d", [foo isEqual:bar]); // true
NSLog(@"%d", [foo isEqualToString:bar]); // true
Copy the code

From the above code, you can see the difference between == and isotropy. The NSString class implements its own unique equivalence method, which must pass a string object as an argument. Faster than the isEqual: method, which also performs additional steps. Because it does not know the type of object receiving the argument.

  • There are two methods in the NSObject protocol for isotropy determination:

    • - (Bool)isEqual:(id)object
    • -(NSUinteger)hash;
  • The default implementation of the NSObject class for these two methods is that objects are equal if and only if their pointer values are exactly equal. (Refer to GNUstep for the following code)

- (BOOL) isEqual: (id)anObject { return (self == anObject); } - (NSUInteger) hash { /* * malloc() must return pointers aligned to point to any data type */ #define MAXALIGN (__alignof__(_Complex long double)) static int shift = MAXALIGN==16 ? 4 : (MAXALIGN==8 ? 3:2); /* We shift left to lose any zero bits produced by the * alignment of the object in memory. */ return (NSUInteger)((uintptr_t)self >> shift); }Copy the code
  • If you want to determine the equivalence of a custom object, you override these two methods. And there is a convention to follow: if the isEqual method determines that two objects are equal, its hash method must return the same value. But the hash method returns the same value, and the isEqual method does not necessarily consider two objects equal.

  • Using the EOCPerson class as an example, we agreed that two EOCPerson objects are equal if all of their fields are equal.

// EOCPerson.h @interface EOCPerson : NSObject @property (nonatomic, copy) NSString *firstName; @property (nonatomic, copy) NSString *lastName; @property (nonatomic, assign) NSUInteger age; @end // eocperson. m - (BOOL)isEqual:(id)object {if (self == object) {return YES; } if ([self class] ! = [object class]) {// Different type, return NO return NO; } EOCPerson *otherPerson = (EOCPerson *)(object); if (! [_firstName isEqualToString: otherPerson. FirstName]) {/ / name, return, NO return NO; } if (! [_lastName isEqualToString: otherPerson. LastName]) {/ / last name, return, NO return NO; } if (_age ! = otherperson.age) {return NO; } return YES; }Copy the code
  • Hash method. According to the equality convention: If two objects are equal, their hash value must be equal. However, objects with the same hash value are not necessarily equal objects.
  • The first implementation: directly return a fixed value
- (NSUInteger)hash {
    return 1337;
}
Copy the code

If written this way, using a Collection (which stands for Array, Dictionary, Set, and so on) creates a performance problem because collection uses the hash value of the object as its index when retrieving the hash table. When adding objects to a collection is implemented using a set, the set may divide the objects into different arrays based on the hash value. When adding objects to a set, we need to find the array associated with the hash value and check each element in order to see whether the existing objects in the array are equal to the new objects to be added. If it is equal, the object is already in the set. If the objects all return the same hash value, adding new objects to a set that already has 100,000 objects will, in the worst case, require traversing all 100,000 objects.

  • The second implementation scheme: based on the value of the instance variable, create a string and return the hash value of the string. This is consistent with the convention. But each time there is extra overhead to create a string object.
- (NSUInteger)hash {
    NSString *stringToHash = [NSString stringWithFormat:@"%@:%@:%i", _firstName, _lastName, _age];
    return [stringToHash hash];
}
Copy the code
  • The third scheme: generate hash values respectively, and finally carry out bit operations to return hash values. This keeps you efficient and keeps the generated hash values within a certain range.
- (NSUInteger)hash {
    NSUInteger firstNameHash = [_firstName hash];
    NSUInteger lastNameHash = [_lastName hash];
    NSUInteger ageHash =_age;
    return firstNameHash ^ lastNameHash ^ ageHash;
}
Copy the code

The equivalence determination method of a particular class

  • In addition toNSStringOutside of class,NSArrayandNSDictionaryClasses also have special isotropy determination methods. The former isisEqualToArray:And the latter forisEqualToDictionay:If the object to which it is being compared is not an array or dictionary, an exception is thrown.
  • If you often need to judge the equivalence of a certain type, you can create an equivalence determination method, so that there is no need to detect the type, improve the speed, the code is easier to read.
  • Write their own isotropic determination method, also need to rewriteisEqual:Method, if they are of the same type, we call our own determination method, otherwise we call our parent class’s method to determine.
- (BOOL)isEqualToPerson:(EOCPerson *)person { if (self == person) { return YES; } if (! [_firstName isEqualToString:person.firstName]) { return NO; } if (! [_lastName isEqualToString:person.lastName]) { return NO; } if (_age ! = person.age) { return NO; } return YES; } - (BOOL)isEqual:(id)object { if ([self class] == [object class]) { return [self isEqualToPerson:(EOCPerson *)object]; } else { return [super isEqual:object]; }}Copy the code

Depth of execution of equivalence determination

  • When creating an isotropy method, you need to decide whether to judge isotropy by the entire object or just a few fields.NSArrayCheck whether the number of objects in the two arrays is the same. If so, call it on the two objects in each corresponding positionisEqual:Methods. If the objects at the corresponding positions are equal, then the two arrays are equal, which is calledDepth equivalence test. Sometimes, instead of comparing all the data individually, you can compare only some of them.
  • Example: HypothesisEOCPersonData from the database, which may contain an attribute, is in the databaseA primary keyIs also a unique identifier. In this case, as long as the unique identifier is the same, it must be the same object. So you don’t have to compareEOCPersonIs each property of the.

The equivalence of mutable classes in a container

  • Be careful when putting mutable objects into containers. Put something incollectionAfter that, it should not change its hash value.collectionEach object is sorted into a different bin array based on its hash value. If an object changes its hash value after it’s put in the box. Then it’s in the wrong box for it. To solve this problem, you need to make sure that the hash value is not computed from the variable part of the object, or that it is put incollectionAnd then you don’t change the object content anymore.
  • Here’s an example: Use oneNSMutableSetAnd a fewNSMutableArrayObject test, first add an array tosetIn the
NSMutableSet *set = [NSMutableSet set]; NSMutableArray *arrayA = @[@1, @2].mutableCopy; [set addObject:arrayA]; NSLog(@"set = %@", set); /* set = {((1,2))} */Copy the code
  • setThere’s an array object, and there’s two objects in the array. Again tosetTo add an array that contains the same objects as the previous array in the same order.
NSMutableArray *arrayB = @[@1, @2].mutableCopy; [set addObject:arrayB]; NSLog(@"set = %@", set); /* set = {((1,2))} */Copy the code
  • setThere’s still only one object in there, because the array we just added is equal to the array we already have, sosetWill not change. At this point, we add a sumsetExisting array different array.
NSMutableArray *arrayC = @[@1].mutableCopy; [set addObject:arrayC]; NSLog(@"set = %@", set); /* set = {((1), (2))} */Copy the code
  • setNow we have two arrays. Next, we changearrayCMake it equal to the array we added before.
[arrayC addObject:@2]; NSLog(@"set = %@", set); /* set = {((1,2), (1,2))} */Copy the code
  • And judging by the print,setThere are two identical arrays in. butsetThe semantics are not allowed to be repeated because we modified themsetObject in. If we copy it nowset
NSSet *setB = [set copy]; NSLog(@"setB = %@", setB); /* setB = {((1,2))} */Copy the code
  • Copy ofsetIt’s just an array object again. Such code is prone to unpredictable results and pitfalls.