你是否真的了解OC对象相等?
标签: objective-c
比较对象
比较两个对象是否相等是一个常用的功能。==
操作符比较的不是对象,而是两个指针本身,一般来说这不是咱们所想要的。要想判断对象是否相等,应该使用 NSObject 协议中声明的isEqual:
方法来判断。一般来说,两个类型不同的对象总是不相等的。
如果你已经有过一些 OC 的编码经验的话,你一定自定义过isEqual:
方法。那么你是碰到过一些奇葩的问题呢?咱们先来看一段代码:
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (BOOL)isEqual:(id)object {
if (self == object) return YES;
if (![object isKindOfClass:[Person class]]) return NO;
return [self.name isEqualToString:[object name]];
}
- (id)copyWithZone:(NSZone *)zone {
Person *person = [[Person allocWithZone:zone] init];
person.name = self.name;
return person;
}
@end
- (void)test {
Person *person = [[Person alloc] init];
person.name = @"Lili";
NSDictionary *dic = @{person: @1};
Person *personCopy = [person copy];
id object = [dic objectForKey:personCopy];
NSLog(@"%@", [person isEqual:personCopy] ? @"YES" : @"NO"); // 输出:YES
NSLog(@"%@", object); // 输出:(null)
}
这段代码定义了类:Person,实现了isEqual:
方法,并实现了NSCopying
协议。在测试中,以 Person 对象作为 key放到了字典中。再以 person 对象 copy 出一个新的对象:personCopy。但用这个 personCopy 作为 key 从字典中去取相应的值时,居然没有取到,输出为(null)。但咱们判断过 person 与 personCopy 对象呀:[person isEqual:personCopy]
,而且它是的确是相等的。
那么问题出在哪了?系统问题?不可能。咱们先看看NSDictionary中对key的要求:
在字典中,keys都是唯一的。也就是说,在一个字典中不会有两个key相等(通过
isEqual:
来判断)。一般来说,一个key可以是任何实现了NSCopying
协议的对象
NSCopying
协议只定义了copyWithZone:
方法,咱们也实现了,不是这个问题。若非问题出在isEqual:
上?这个方法声明在NSObject
协议中。来看看这个方法其中一段说明:
如果两个对象是相等的,那么它们就必须有相同的哈希码(hash value)。如果你的自定义类型定义了
isEqaul:
方法,并打算将其实例需要放到集合中,这点尤为重要。你应该确保在定义了isEqual:
方法的同时,也定义hash
方法。
也就是说,NSObject
协议中有两个用于判断等同性的方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
NSObject
类对这两个方法的默认实现是:当且仅当其“指针值”完全相等时,这两个对象才相等。若想在自定义类中正确实现这些方法,就必须先理解其约定。如果isEqual:
方法判定两个对象相等,那么其hash
方法也必须返回同一值。但是,如果两个对象的hash
方法返回同一个值,那么isEqual:
方法未必会认为两者相等。
哦,原来是这样呀,看来问题就是出在这里了。那么咱们写一个hash
方法出来看看:
// Person.m
- (NSUInteger)hash {
return [self.name hash];
}
// Test
- (void)test {
Person *person = [[Person alloc] init];
person.name = @"Lili";
NSDictionary *dic = @{person: @1};
Person *personCopy = [person copy];
id object = [dic objectForKey:personCopy];
NSLog(@"%@", [person isEqual:personCopy] ? @"YES" : @"NO"); // 输出:YES
NSLog(@"%@", object); // 输出:1
}
OK,这回没有问题了。
结论:实现isEqual:
方法的同时,要实现hash
方法!
hash 要注意的地方
在集合中(如 NSDictionary 或 set)会使用对象的哈希码来决定对象所存储的位置。 如果一个可变对象(mutable object)加到这样一个集合中的后,其hash
方法所返回的哈希码必须保持一致,不能改变。如下面代码 所示:
- (void)test {
Person *person = [[Person alloc] init];
person.name = @"Lili";
NSMutableDictionary *dic = [@{person: @1} mutableCopy];
NSLog(@"%@", dic); // 输出:{ Lili = 1; }
Person *personOther = [[Person alloc] init];
personOther.name = @"jany";
dic[personOther] = @2;
NSLog(@"%@", dic); // 输出:{ Lili = 1; jany = 2; }
Person *personSKey = [dic allKeys][1];
personSKey.name = @"Lili";
NSLog(@"%@", dic); // 输出:{ Lili = 1; Lili = 1; } 错误!
}
上面的字典dic被整得乱套了有木有~ 完离这种写法,珍爱生命!