1约谈、什么是等同性
根據(jù)等同性
來(lái)比較對(duì)象是一個(gè)非常有用的功能碗啄。我們常用的 ==
操作符比較质和,但是==
比較的是指針本身,而不是其所指的對(duì)象稚字。如果兩個(gè)對(duì)象指針不同饲宿,但是其它的所有屬性等全都相同厦酬,這兩個(gè)對(duì)象可以說(shuō)是等同的
。
常見(jiàn)的比較方法有NSString瘫想、NSArray仗阅、NSDictionary的比較方法‘;它們不是比較指針国夜,而是比較對(duì)象除指針之外的减噪。
- (BOOL)isEqualToString:(NSString *)aString;
- (BOOL)isEqualToArray:(NSArray<ObjectType> *)otherArray;
- (BOOL)isEqualToDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;
...
就拿isEqualToArray:
來(lái)說(shuō)
NSArray *array1 = @[@1];
NSArray *array2 = @[@1];
NSLog(@"array1: %p", &array1);
NSLog(@"array2: %p", &array2);
NSLog(@"result: %d", [array1 isEqualToArray:array2]);
結(jié)果:
array1: 0x7ffee26978f0
array2: 0x7ffee26978e8
result: 1
說(shuō)明array1
和array2
是兩個(gè)不同的對(duì)象(指針不同),卻是兩個(gè)等同的對(duì)象车吹。
2筹裕、如何判斷兩個(gè)對(duì)象是否等同
NSObject協(xié)議中有兩個(gè)用于判斷等同性的方法:
- (BOOL)isEqual:(id)object;
- (NSInteger)hash;
NSObject類(lèi)對(duì)這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)是:當(dāng)且僅當(dāng)對(duì)象的“指針值”
完全相等的時(shí)候,這兩個(gè)對(duì)象才相等窄驹。
這就會(huì)出現(xiàn)一個(gè)問(wèn)題:如果我們希望一個(gè)NSSet
中不存在等同的對(duì)象朝卒,而默認(rèn)的isEqual
和hash
方法只會(huì)根據(jù)指針來(lái)判斷,依然會(huì)把指針不同的等同對(duì)象放入Set中乐埠,顯然抗斤,這無(wú)法做到我們想要的效果。所以現(xiàn)在我們要重寫(xiě)這兩個(gè)方法丈咐。
1. 創(chuàng)建對(duì)象People豪治,包含name和age屬性
//People.h
@interface People : NSObject
//name
@property (nonatomic, strong)NSString *name;
//age
@property (nonatomic, assign)NSInteger age;
@end
2. 模仿isEqualToArray:
方法,創(chuàng)建isEqualToPeople:
方法
- (BOOL)isEqualToPeople:(People *)people {
if (self == people) return YES; //如果指針相同扯罐,肯定是同一個(gè)對(duì)象
if (![self.name isEqualToString:people.name]) return NO;
if (self.age != people.age) return NO; //如果對(duì)象的屬性不同负拟,肯定不等同
return YES; //如果上面的條件都通過(guò)了,則是等同的對(duì)象
}
同時(shí)重寫(xiě)isEqual:
方法
- (BOOL)isEqual:(id)object {
if ([object class] == [self class]) {
return [self isEqualToPeople:object];
}else {
return [super isEqual:object];
}
}
3. 重寫(xiě)hash
方法
什么是哈希碼歹河?
哈希碼并不是完全唯一的掩浙,它是一種算法,讓同一個(gè)類(lèi)的對(duì)象按照自己不同的特征盡量的有不同的哈希碼秸歧,但不表示不同的對(duì)象哈希碼完全不同厨姚。也有相同的情況,看程序員如何寫(xiě)哈希碼的算法键菱。
根據(jù)等同性約定:
若兩個(gè)對(duì)象等同谬墙,則起哈希碼也相等,但是兩個(gè)哈希嗎相同的對(duì)象卻不一定相等经备。
所以拭抬,相等的對(duì)象双霍,必須有相等的哈希碼傲须,要盡量讓不同對(duì)象的哈希碼不會(huì)重復(fù);
我們可以根據(jù)對(duì)象的屬性值哮塞,生成哈希碼纷闺;但是這樣生成的哈希碼依然會(huì)有重復(fù)的可能算凿,但是并不會(huì)有太大的影響份蝴。
- (NSUInteger)hash {
NSInteger nameHash = [_name hash];
NSInteger ageHash = _age;
NSLog(@"hash: %ld", nameHash ^ ageHash);
return nameHash ^ ageHash;
}
我們知道,isEqual:
方法或者isEqualToPeople:
方法是會(huì)被程序員主動(dòng)調(diào)用以判斷對(duì)象是否等同氓轰,但是hash
方法就沒(méi)有isEqual
方法的存在感那么強(qiáng)了婚夫。
3、何時(shí)會(huì)調(diào)用hash
方法
這個(gè)問(wèn)題可以被解釋成這樣:
何時(shí)需要調(diào)用對(duì)象的哈希碼署鸡,作為或者生成一個(gè)識(shí)別碼案糙,用以識(shí)別不同的對(duì)象。
我們知道储玫,字典是根據(jù)key來(lái)尋值的,所以一個(gè)字典里不能有相同的key萤皂。
1撒穷、當(dāng)對(duì)象作為字典的key值時(shí),會(huì)調(diào)用hash
方法裆熙,幫助對(duì)象稱(chēng)為字典中獨(dú)一無(wú)二的key
People *p2 = [People new];
p2.name = @"p2";
p2.age = 20;
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:@"Object" forKey:p2];
注意實(shí)現(xiàn)對(duì)象的copyWithZone
方法
2端礼、NSSet
是不允許有重復(fù)的對(duì)象的,所以向NSMutableSet
中添加對(duì)象時(shí)入录,會(huì)調(diào)用對(duì)象的hash
方法蛤奥,用以比較該對(duì)象是否已存在于集合中。
People *p1 = [People new];
p1.name = @"p1";
p1.age = 12;
NSMutableSet *set = [NSMutableSet set];
[set addObject:p1];
需要注意的是:把可變對(duì)象加入到集合set中后僚稿,盡量不要改變其哈希碼了
原因:
加入集合時(shí)凡桥,新加入對(duì)象的哈希碼和集合中已存在的對(duì)象的哈希碼是不同的,如果在對(duì)象加入集合之后更改對(duì)象的哈希碼蚀同,以至于對(duì)象的哈希碼和集合中的某一個(gè)對(duì)象的哈希碼相同缅刽,那就會(huì)出現(xiàn)一些問(wèn)題。
例如:
People *p3 = [People new];
p3.name = @"Jack";
p3.age = 20;
People *p4 = [People new];
p4.name = @"Koko";
p4.age = 20;
NSMutableSet *set4 = [NSMutableSet set];
[set4 addObject:p3];
[set4 addObject:p4];
我們上面定義了hash
方法的實(shí)現(xiàn)蠢络,hash碼是根據(jù)People對(duì)象的name和age來(lái)設(shè)定的衰猛,所以p3和p4是可以加入到集合set4中的。
現(xiàn)在刹孔,我們來(lái)更改一下p4啡省,使p4和p3的哈希碼相同,看看會(huì)發(fā)送什么髓霞?
p4.name = @"Jack";
此時(shí)卦睹,p3和p4是等同的
。
我們看到方库,不能存在重復(fù)對(duì)象的Set中居然存在了兩個(gè)等同的對(duì)象分预!
那么我們?nèi)绾伟堰@兩個(gè)等同的對(duì)象,去掉一個(gè)呢薪捍?
NSSet *set5 = [set4 copy];
結(jié)果:
通過(guò)copy
方法笼痹,深拷貝了一個(gè)全新的集合配喳,這個(gè)新集合就像逐個(gè)向新集合中添加對(duì)象而創(chuàng)建出來(lái)的。這個(gè)問(wèn)題還是根據(jù)開(kāi)發(fā)者當(dāng)前的需求從而決定是否需要對(duì)set進(jìn)行去重操作
凳干。