对象等同性判断

335 阅读3分钟

对象等同性

概述

在实际的开发过程当中,经常需要比较对象是否相同。但是,==操作符比较出来的结果可能并不是想要的,因为这个操作符比较的是指针本身,并不是对象。所以大部分情况下,比价对象应该使用NSObject协议中的isEqual:

  • 一般来说,类型不同的对象总是不相等的。
  • 某些类会提供自己的等同性判定方法。

比如:

NSString *foo = @"Student 123";
NSString *bar = [NSString stringWithFormat:@"Student %i",123];
BOOL equalA = (foo == bar); //equalA = NO
BOOL equalB = [foo isEqaul:bar]; //equalA = NO
BOOL equalC = [foo isEqualToString:bar]; //equalC = YES

这里可以看出,NSString有着自己的等同性判断方法isEqualToString:。调用这个方法会比调用isEqual:方法快,因为后者需要执行额外的步骤检测受测对象的类型。

判断过程

判断对象等同性在NSObject协议中有两个关键方法:

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;

这两个方法的默认实现是两个对象内存地址完全(指针值)相等时,这两个对象才相等。在自定义对象中,正确重写这两个方法的步骤应该是:

重写isEqual:返回YES --> Hash值相等 --> 调用isEqual:方法判断对象等同

举例

@interface EOCPerson : NSObject
@property (nonatomic, copy)NSString *firstName;
@property (nonatomic, copy)NSString *lastName;
@property (nonatomic, assign)NSUInteger *age;
@end

很明显,当这个对象所有字段都相同时,就可以认定两个对象相等了。所以重写isEqual:方法:

- (BOOL)isEqual:(id)object {
    ///判断指针相等
    if (self == object) return YES;
    ///不同类不相等
    if ([self class] != [object class]) return NO;
    
    
    EOCPerson *otherPerson = (EOCPerson*)object;
    if (![_firstName isEqualToString:otherPerson.firstName]) {
        return NO;
    }
    if (![_lastName isEqualToString:otherPerson.lastName]) {
        return NO;
    }
    if (_age != otherPerson.age) {
        return NO;
    }
    return YES;
}

首先判断了指针是否相等,之后判断所属的类。不过在判断类的时候,需要特别注意有继承的情况,如果判断子类是否与父类相等,就不能这么粗暴的重写isEqual方法了,需要根据情况,自定义一个子类和父类的比较规则。

之后就需要实现hash方法了,最先想到的是这样:

- (NSUInterger)hash {
    return 1024;
}

这样实现之后确实能实现两个EOCPerson对象的等同性判断了。但在一些场景中会产生性能问题。比如一个用set实现的collection,collocation在检索哈希表时,会用哈希值作为索引。每次向collection添加新对象时,优先会检查哈希值相同的相关对象,再判断等同性。所以,如果已经有了10000个对象在set中,此时添加一个新对象,按照上个例子中的实现,就需要遍历完10000个对象才能完成插入对象的操作。

那么我们将上述例子做一下改进:

- (NSUInterger)hash {
    NSString *stringToHash = [NSString stringWithFormat:@"%@:%@:%i", _firstName, _LastName, _age];
    return [stringToHash hash];
}

这么做就能避免刚才所说的问题了,提高set操作时的检索效率,但字符串操作相对返回一个单一值来说仍然是一个耗时过程,会影响set的性能。

再进一步的优化一下:

- (NSUInterger)hash {
    NSUInterger firstNameHash = [_firstName hash];
    NSUInterger lastNameHash = [_lastName hash];
    NSUInterger ageHash = _age;
    return firstNameHash ^ lastNameHash ^ ageHash;
}

这样既解决了哈希索引的问题,有提高了哈希值生成的效率。所以哈希值的生成有两个注意点:

  • 减少碰撞频率
  • 降低运算复杂度

等同性执行深度

在判断数组的是否等同的时候,需要判断数组内所有对象都相同,我们把这个叫做“深度等同性判断”。但有时候,根据不同的场景,不需要判断所有对象或者对象的所有属性都一样,这样就可以降低等同性判断的执行深度。

比如数组的数据是从数据库里取出的,这个时候只要判断数据对象对应的主键是否相等,便能判断这个对象的等同性。