对象等同性
概述
在实际的开发过程当中,经常需要比较对象是否相同。但是,==
操作符比较出来的结果可能并不是想要的,因为这个操作符比较的是指针本身,并不是对象。所以大部分情况下,比价对象应该使用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;
}
这样既解决了哈希索引的问题,有提高了哈希值生成的效率。所以哈希值的生成有两个注意点:
- 减少碰撞频率
- 降低运算复杂度
等同性执行深度
在判断数组的是否等同的时候,需要判断数组内所有对象都相同,我们把这个叫做“深度等同性判断”。但有时候,根据不同的场景,不需要判断所有对象或者对象的所有属性都一样,这样就可以降低等同性判断的执行深度。
比如数组的数据是从数据库里取出的,这个时候只要判断数据对象对应的主键是否相等,便能判断这个对象的等同性。