1、oc中如何判断两个对象是否相等?
使用isEqual方法判断两个对象是否相等。它和==判断相等有什么区别?
对于基本类型==比较的是值;对于对象类型,==比较的是对象的地址,即是否为同一个对象;
一个简单的例子:
UIColor *color1 = [UIColor colorWithRed:1 green:1 blue:1 alpha:1];
UIColor *color2 = [UIColor colorWithRed:1 green:1 blue:1 alpha:1];
result = [color1 isEqual:color2];
NSLog(@"color1 == color2--%d, [color1 isEqual:color2]---%d", color1==color2, result);
打印结果如下:
color1 == color2--0, [color1 isEqual:color2]---1
2、如何重写isEqual方法
为什么要重写?先来看看NSObject是怎么实现isEqual的
- (BOOL)isEqual:(id)anObject {
return (self == anObject);
}
参考的是gunStep的源码。如果不重写的话,任何两个对象的比较结果都是No,因为它比较的是两个对象的地址。
在cocoa framework中常见的NSString、NSDate、NSArray、NSDictionary、NSSet等都有重写isEqual方法。
对于自定义的类型来说,如果想要比较两个对象是否相同,就要重写isEqual方法。
我们自定义一个Person类来进行简单的说明。
@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *name;
@end
在Person类中实现isEqual方法
- (BOOL)isEqual:(id)object {
// ==运算符判断是否是同一对象, 因为同一对象必然完全相同
if (self == object) {
return YES;
}
//判断是否是同一类型, 这样不仅可以提高判等的效率, 还可以避免隐式类型转换带来的潜在风险
if (![object isKindOfClass:[Person class]]) {
return NO;
}
return [self isEqualToPerson:object];
}
- (BOOL)isEqualToPerson:(Person *)person {
//判断person是否是nil, 做参数有效性检查
if (!person) {
return NO;
}
//对各个属性分别使用默认判等方法进行判断
BOOL equalNames = (!self.name && !person.name) || [self.name isEqualToString:person.name];
BOOL equalAges = (!self.age && !person.age) || self.age == person.age;
//返回所有属性判等的与结果
return equalNames && equalAges;
}
3、为什么要有hash方法?
这个问题要从Hash Table这种数据结构说起。
首先我们看下如何在数组中查找摸个成员:
- 遍历数组中的成员;
- 将取出的值与目标值比较,如果相等,则返回该成员;
数组在未排序的情况下,查找的时间复杂度是O(n)。
为了提高查找的速度,Hash Table出现了。
当成员被加入到Hash Table中是,会给它分配一个hash值,以标识该成员在集合中的位置。
通过这个位置标识可以将查找的时间复杂度优化到O(1),当然如果多个成员都是同一个位置标识,那么查找就不能达到O(1)了。
重点来了:
分配的这个hash值(即用于查找集合中成员的位置标识),就是通过hash方法计算得来的,且hash方法返回的hash值最好唯一。
和数组相比,基于hash值索引的Hash Table查找摸个成员的过程就是:
- 通过hash值直接找到查找目标的位置;
- 如果目标位置上有多个相同的hash值成员,此时再按照数据方式进行查找;
4、hash方法什么时候会被调用?
- (void)test2 {
Person *p1 = [[Person alloc] init];
p1.name = @"张三"; p1.age = 11;
Person *p2 = [[Person alloc] init];
p2.name = @"张三"; p2.age = 11;
NSLog(@"array---------------");
NSMutableArray *array = [NSMutableArray array];
[array addObject:p1]; [array addObject:p2];
NSLog(@"array--%ld", array.count);
NSLog(@"set-----------------");
NSMutableSet *set = [NSMutableSet set];
[set addObject:p1];
[set addObject:p2];
NSLog(@"set--%ld", set.count);
NSLog(@"dict1----------------");
NSMutableDictionary *dict1 = [NSMutableDictionary dictionary];
[dict1 setObject:p1 forKey:@"p1"];
[dict1 setObject:p2 forKey:@"p2"];
NSLog(@"dict1--%ld", dict1.count);
NSLog(@"dict2-----------------");
NSMutableDictionary *dict2 = [NSMutableDictionary dictionary];
[dict2 setObject:@"p1" forKey:p1];
[dict2 setObject:@"p2" forKey:p2];
NSLog(@"dict2--%ld", dict2.count);
}
重写Person类的hash方法
- (NSUInteger)hash {
NSUInteger hash = [super hash];
NSLog(@"hash=%ld", hash); return hash;
}
打印结果
重写isEqual和hash1[68464:3862796] array--2
重写isEqual和hash1[68464:3862796] set-----------------
重写isEqual和hash1[68464:3862796] hash=105553131306432
重写isEqual和hash1[68464:3862796] hash=105553131306464
重写isEqual和hash1[68464:3862796] set--2
重写isEqual和hash1[68464:3862796] dict1----------------
重写isEqual和hash1[68464:3862796] dict1--2
重写isEqual和hash1[68464:3862796] dict2-----------------
重写isEqual和hash1[68464:3862796] hash=105553131306432
重写isEqual和hash1[68464:3862796] hash=105553131306464
重写isEqual和hash1[68464:3862796] dict2--2
从打印的结果看
hash方法只有在对象被添加到NSSet和设置为NSDictionary的key时会被调用
NSSet添加新成员是需要根据hash值来快速查找成员,以保证集合中是否已经存在该成员。
NSDictionary在查找key是,也是利用key的hash值来提高查找的效率。
5、hash和isEqual的关系
为了优化判等的效率,基于hash的NSSet和NSDictionary在判段成员是否相等时,会这样做:
- 成员的hash值是否和目标hash值相等,如果相同进入2,如果不等,就直接判断不相等;
- hash值相同(即1)的情况下,再进行对象判等,作为判等条件;
6、如何重写hash方法
这么重写hash方法
- (NSUInteger)hash {
return [super hash];
}
来看下[super hash] 的值是什么
Person *p1 = [[Person alloc] init];
p1.name = @"张三"; p1.age = 11;
Person *p2 = [[Person alloc] init];
p2.name = @"张三"; p2.age = 11;
result = [p1 isEqual:p2];
NSLog(@"%d", result);
NSLog(@"%ld---%ld", [p1 hash], (NSUInteger)p1);
NSLog(@"%ld---%ld", [p2 hash], (NSUInteger)p2);
打印结果如下
重写isEqual和hash1[68808:3878726] 105553123180000---105553123180000
重写isEqual和hash1[68808:3878726] 105553123180032---105553123180032
可以看出,[super hash]返回的就是对象的内存地址
我们添加如下两个对象到NSSet中试试
Person *p1 = [[Person alloc] init];
p1.name = @"张三";
p1.age = 11;
Person *p2 = [[Person alloc] init];
p2.name = @"张三";
p2.age = 11;
NSMutableSet *set = [NSMutableSet set];
[set addObject:p1];
[set addObject:p2];
NSLog(@"count---%ld", set.count);
此时打印结果如下:
重写isEqual和hash1[69895:3927266] hash=105553129036576
重写isEqual和hash1[69895:3927266] hash=105553129036352
重写isEqual和hash1[69895:3927266] hash=105553129036384
重写isEqual和hash1[69895:3927266] 调用了isEqual
重写isEqual和hash1[69895:3927266] count---2
isEqual相等的两个对象都加入到了NSSet中,所以直接返回[super hash]是不正确的。
那么hash方法的最佳实践是什么呢? 大神Mattt Thompson 给出的结论是:
对关键属性的hash值进行位异或运算作为hash值
对于上面Person类的hash方法实现如下:
- (NSUInterger)hash {
return [self.name hash] ^ [@(self.age) hash];
}