类的相等性

当你要实现相等性的时候记住这个约定:你需要同时实现isEqualhash方法。如果两个对象是被isEqual认为相等的,它们的 hash 方法需要返回一样的值。但是如果 hash 返回一样的值,并不能确保他们相等。

这个约定当对象被存储在集合中(如 NSDictionaryNSSet 在底层使用 hash 表数据的数据结构)的时候,用来查找这些对象的。

@implementation ZOCPerson
- (BOOL)isEqual:(id)object {
    if (self == object) {
        return YES;
    }
    if (![object isKindOfClass:[ZOCPerson class]]) {
        return NO;
    }
    // check objects properties (name and birthday) for equality (检查对象属性(名字和生日)的相等性
    ...
    return propertiesMatch;
}
- (NSUInteger)hash {
    return [self.name hash] ^ [self.birthday hash];
}
@end

一定要注意 hash 方法不能返回一个常量。这是一个典型的错误并且会导致严重的问题,因为实际上hash方法的返回值会作为对象在 hash 散列表中的 key,这会导致 hash 表 100% 的碰撞。

你总是应该用 isEqualTo: 这样的格式实现一个相等性检查方法。如果你这样做,会优先调用这个方法来避免上面的类型检查。

一个完整的 isEqual 方法应该是这样的:

- (BOOL)isEqual:(id)object {
    if (self == object) {
        return YES;
    }
    if (![object isKindOfClass:[ZOCPerson class]]) {
        return NO;
    }
    return [self isEqualToPerson:(ZOCPerson *)object];
}
- (BOOL)isEqualToPerson:(Person *)person {
    if (!person) {
        return NO;
    }
    BOOL namesMatch = (!self.name && !person.name) ||
                       [self.name isEqualToString:person.name];
    BOOL birthdaysMatch = (!self.birthday && !person.birthday) ||
                           [self.birthday isEqualToDate:person.birthday];
    return haveEqualNames && haveEqualBirthdays;
}

译者注: 一般而言我们会直接调用自定义的isEqualTo__ClassName__:方法,对类的实例判等。

像相等性的开篇已经提到的那样,这里应该复写isEqual:方法,因为NSObject的isEqual:方法显然不会考虑我们自定义类的类型判断及属性的相等性。当我们自定义的类的对象处在无序集合中被查找时,会自动调用isEqual:。同样的该类的hash方法,也会在集合查找对象的时候被使用,我们也可以通过复写hash方法以达到用自己的标准来判定对象是否hash等同。

我们实现的hash方法应该建立在系统提供的各种对象的hash方法之上(像开篇的例程那样)。不推荐自己去实现某种hash算法来替代系统提供的hash算法,这一般而言会大大影响性能或者准确性,系统提供的hash算法已经经过无数次修缮,足以满足你的要求。

一个对象实例的 hash 计算结果应该是确定的。当它被加入到一个容器对象(比如 NSArray, NSSet, 或者 NSDictionary)的时候这是很重要的,否则行为会无法预测(所有的容器对象使用对象的 hash 来查找或者实施特别的行为,如确定唯一性)这也就是说,应该用不可变的属性来计算 hash 值,或者,最好保证对象是不可变的。