前言
對(duì)數(shù)據(jù)的等同性判斷包括對(duì)基本數(shù)據(jù)類型等同性
的判斷和對(duì)象等同性
的判斷。對(duì)基本數(shù)據(jù)類型等同性的判斷是非常簡單的,比如對(duì)兩個(gè)NSInteger類型的變量等同性判斷,我們直接使用關(guān)系運(yùn)算符“==”即可。
相比于基本數(shù)據(jù)類型等同性熄诡,對(duì)象等同性的判斷就稍顯復(fù)雜。按照大神Mattt Thompson
的說法徒扶,對(duì)象的等同性
包括相等性
和本體性
粮彤。從字面不難發(fā)現(xiàn),相等性是指:兩個(gè)對(duì)象的值是否相等
姜骡。本體性是指:兩個(gè)對(duì)象本質(zhì)上是否是同一個(gè)對(duì)象
导坟。
關(guān)系運(yùn)算符"=="不僅可以應(yīng)用在基本數(shù)據(jù)類型上,還可以應(yīng)用在兩個(gè)對(duì)象類型的對(duì)象上圈澈。不過惫周,按照==”比較兩個(gè)對(duì)象,本質(zhì)上是對(duì)兩個(gè)對(duì)象指針地址的比較康栈,即對(duì)象本體性的判斷递递。單純的比較兩個(gè)對(duì)象的指針并不能完全滿足要求喷橙,因?yàn)閷?duì)象的等同性不僅包括本體性,還包括相等性登舞。有時(shí)候指向兩個(gè)對(duì)象的指針雖然不相同贰逾,但是兩個(gè)對(duì)象的值是相同的,我們也認(rèn)為其是相同的菠秒,即相等性疙剑。換句話說,單純的通過比較兩個(gè)對(duì)象的指針來判斷等同性總是太過苛刻践叠。而對(duì)于自定義的類型言缤,開發(fā)中經(jīng)常要對(duì)兩個(gè)對(duì)象的相等性進(jìn)行判斷,即對(duì)兩個(gè)對(duì)象每個(gè)屬性進(jìn)行比較禁灼。如果兩個(gè)對(duì)象的類型相同管挟,且屬性值都一樣,我們也會(huì)認(rèn)為其是相等的弄捕。如果對(duì)象是集合類型僻孝,比如數(shù)組,相等性檢查要求我們對(duì)兩個(gè)數(shù)組相同位置的元素進(jìn)行逐個(gè)比較察藐。
NSFoundation提供的一些方法
Objective-C的NSFoundation框架中給我們提供了很多判斷對(duì)象等同性的方法皮璧。比如:
// NSString類提供了判斷兩個(gè)NSString對(duì)象是否相等的方法
- (BOOL)isEqualToString:(NSString *)aString;
// NSArray類提供了判斷兩個(gè)NSAarray對(duì)象是否相等的方法
- (BOOL)isEqualToArray:(NSArray<ObjectType> *)otherArray;
// NSDictionary類提供了判斷兩個(gè)NSDictionary對(duì)象是否相等的方法
- (BOOL)isEqualToDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;
// NSSet類提供了判斷兩個(gè)NSSet對(duì)象是否相等的方法
- (BOOL)isEqualToSet:(NSSet<ObjectType> *)otherSet;
// ......
- (BOOL)isEqualToData:(NSData *)other;
- (BOOL)isEqualToNumber:(NSNumber *)number;
- (BOOL)isEqualToValue:(NSValue *)value;
- (BOOL)isEqualToTimeZone:(NSTimeZone *)aTimeZone;
- (BOOL)isEqualToDate:(NSDate *)otherDate;
- (BOOL)isEqualToOrderedSet:(NSOrderedSet<ObjectType> *)other;
- (BOOL)isEqualToHashTable:(NSHashTable<ObjectType> *)other;
- (BOOL)isEqualToIndexSet:(NSIndexSet *)indexSet;
前面說舟扎,對(duì)于NSFoundation框架中的一些類分飞,蘋果已經(jīng)為我們提供了現(xiàn)成的等同性判斷的方法。比如NSString提供的判斷兩個(gè)字符串對(duì)象是否相等的方法- (BOOL)isEqualToString:
睹限。
那么你可能會(huì)問:NSString類默認(rèn)提供了比較字符串等同性的方法譬猫,而那些繼承自NSObject基類的自定義類,我們該怎么判斷等同性呢羡疗?不用擔(dān)心染服,NSObject類的協(xié)議已經(jīng)默認(rèn)提供了- (BOOL)isEqual:(id)object;
方法,且NSObject類也遵守并實(shí)現(xiàn)了NSObject協(xié)議中的isEqual:方法叨恨。我們可以通過調(diào)用- (BOOL)isEqual:
方法來檢驗(yàn)兩個(gè)NSObject對(duì)象的等同性柳刮。其實(shí),個(gè)人認(rèn)為痒钝,NSString的- (BOOL)isEqualToString:
就是在- (BOOL)isEqual:
基礎(chǔ)之上進(jìn)行的擴(kuò)展秉颗。因?yàn)镹SString類繼承自NSObject這個(gè)基類,我們也可以使用- (BOOL)isEqual:
方法對(duì)兩個(gè)字符串進(jìn)行比較送矩。但是不建議這么做蚕甥,因?yàn)橄到y(tǒng)已經(jīng)給我們提供了現(xiàn)成的API,調(diào)用- (BOOL)isEqualToString:
比調(diào)用- (BOOL)isEqual:
方法快栋荸。后者還要執(zhí)行額外的步驟菇怀,因?yàn)樗恢朗軠y對(duì)象的真實(shí)類型凭舶。
覆寫NSObject類的- (BOOL)isEqual:方法
NSObject類對(duì)- (BOOL)isEqual:的默認(rèn)實(shí)現(xiàn)是:當(dāng)且僅當(dāng)被比較的兩個(gè)對(duì)象的指針值相等時(shí),才被認(rèn)為相等
。即仿野,isEqual:的默認(rèn)實(shí)現(xiàn)就是對(duì)對(duì)象本體性的判斷稍算。前面已經(jīng)說過,但對(duì)于自定義類型和集合類型义屏,這種默認(rèn)的判斷有時(shí)候太過苛刻。針對(duì)于這種情況蜂大,如果有判斷自定義對(duì)象等同性的需求闽铐,我們需要覆寫- (BOOL)isEqual:
方法。
- (BOOL)isEqual:(id)object{
// 兩個(gè)對(duì)象指針相等奶浦,其指向同一塊內(nèi)存兄墅,則肯定相等。
if (self == object) {
return YES;
}
// 一般來說澳叉,如果兩個(gè)對(duì)象的類型完全不同隙咸,則肯定不等。
if ([self class] != [object class]) {
return NO;
}
EOCPerson *otherPerson = (EOCPerson *)object;
// 兩個(gè)對(duì)象相應(yīng)的屬性如果不等成洗,則也認(rèn)為不等(忽略繼承和多態(tài))五督。
if (![self.firstName isEqualToString:otherPerson.firstName]){
return NO;
}
if (![self.lastName isEqualToString:otherPerson.lastName]){
return NO;
}
if (self.age != otherPerson.age) {
return NO;
}
// 如果屬性也相等,則認(rèn)為相等瓶殃。
return YES;
}
上面的EOCPerson類充包,實(shí)現(xiàn)了NSObject協(xié)議的- (BOOL)isEqual:方法,首先遥椿,直接判斷兩個(gè)指針是否相等基矮,若相等則其均指向同一個(gè)對(duì)象,所以受測對(duì)象肯定相等冠场。然后家浇,比較兩個(gè)受測對(duì)象所屬的類,若不屬于同一個(gè)類(忽略多態(tài))碴裙,則認(rèn)為兩對(duì)象不相等钢悲。最后,檢查兩個(gè)對(duì)象的屬性是否相等舔株,如果對(duì)象只要有某個(gè)屬性不相等莺琳,就認(rèn)為兩個(gè)對(duì)象不相等,否則對(duì)象相等督笆。
EOCPerson *p1 = [[EOCPerson alloc] init];
p1.firstName = @"VV";
p1.lastName = @"S";
EOCPerson *p2 = [[EOCPerson alloc] init];
p2.firstName = @"VV";
p2.lastName = @"S";
BOOL isEqual = [p1 isEqual:p2];
NSLog(@"isEqual == %d",isEqual); // isEqual == 1
上面我們覆寫EOCPerson類的isEqual:方法時(shí)芦昔,沒有考慮多態(tài)的情況,開發(fā)中如果存在繼承娃肿,我們還需要對(duì)兩個(gè)對(duì)象的類型進(jìn)行比較咕缎,直接調(diào)用NSObject類型查詢方法- (BOOL)isKindOfClass:(Class)aClass;
即可珠十。
- (BOOL)isEqual:(id)object{
// return [super isEqual:object];
// 考慮多態(tài)
if (![self isKindOfClass:[object class]] && ![object isKindOfClass:[self class]]) {
return NO;
}
// 兩個(gè)對(duì)象指針相等,其指向同一塊內(nèi)存凭豪,則肯定相等焙蹭。
if (self == object) {
return YES;
}
EOCPerson *otherPerson = (EOCPerson *)object;
// 兩個(gè)對(duì)象相應(yīng)的屬性如果不等,則也認(rèn)為不等嫂伞。
if (![self.firstName isEqualToString:otherPerson.firstName]){
return NO;
}
if (![self.lastName isEqualToString:otherPerson.lastName]){
return NO;
}
if (self.age != otherPerson.age) {
return NO;
}
// 如果屬性也相等孔厉,則認(rèn)為相等。
return YES;
}
Hash方法
Hash一詞經(jīng)常被譯作“哈咸”撰豺,有時(shí)候也被譯作“雜湊”“散列”。因此拼余,“hash table”有時(shí)候被譯作“哈希表”污桦,也有人稱之為“散列表”。我們只需要知道他們表達(dá)的是同一個(gè)意思匙监。
1.為什么要有Hash方法
根據(jù)約定:如果兩個(gè)對(duì)象相等凡橱,則其哈希值也相等,但是如果兩個(gè)哈希值相等亭姥,則對(duì)象未必相等稼钩。這是能否覆寫isEqual:
方法的關(guān)鍵。
另外达罗,我們知道坝撑,哈希也是會(huì)存在碰撞的。即氮块,兩個(gè)對(duì)象如果相等則哈希值肯定相等绍载。但兩個(gè)對(duì)象的哈希值如果相等,則這兩個(gè)對(duì)象也不一定相等滔蝉。
哈希表是一種數(shù)據(jù)結(jié)構(gòu),經(jīng)常被用來實(shí)現(xiàn)set和dictionary塔沃。我們知道蝠引,set和dictionary都屬于collection(集合)的一種形式。set和dictionary相對(duì)于array而言蛀柴,是其查詢速度是比較快的螃概,事件復(fù)雜度僅為O(1)。而對(duì)于一個(gè)無序的array鸽疾,查詢某個(gè)元素的事件復(fù)雜度是O(n)(其中n為數(shù)組的長度)吊洼。這其中hash就起到了至關(guān)重要的作用。
2.Hash方法的默認(rèn)實(shí)現(xiàn)
hash的默認(rèn)實(shí)現(xiàn)是:返回對(duì)象的內(nèi)存地址作為哈希值
制肮。即冒窍,NSObject類實(shí)現(xiàn)的hash方法递沪,本質(zhì)上是返回的對(duì)象的內(nèi)存地址。乍一想综液,這種默認(rèn)實(shí)現(xiàn)是可行的款慨,但是對(duì)于一些數(shù)據(jù)類型是不行的,比如我們自定義的類型谬莹。
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
EOCPerson *p1 = [[EOCPerson alloc] init];
p1.firstName = @"VV";
p1.lastName = @"S";
EOCPerson *p2 = [[EOCPerson alloc] init];
p2.firstName = @"VV";
p2.lastName = @"S";
BOOL isEqual = [p1 isEqual:p2];
NSLog(@"isEqual == %d",isEqual);
NSMutableSet *set = [NSMutableSet set];
[set addObject:p1];
[set addObject:p2];
NSLog(@"count == %ld",[set count]); // count == 2
如上檩奠,我們沒有覆寫默認(rèn)的isEqual:方法和默認(rèn)的hash方法。p1和p2雖然指針不同附帽,但是對(duì)象的屬性都是完全相同的埠戳。我們把本質(zhì)上兩個(gè)完全相同的對(duì)象插入到set這種數(shù)據(jù)類型中,set應(yīng)該是可以自動(dòng)去重的蕉扮。即乞而,正確的情況下set應(yīng)該只有一個(gè)對(duì)象。但是因?yàn)閔ash方法默認(rèn)返回指針地址作為哈希值慢显,導(dǎo)致set中出現(xiàn)了兩個(gè)本質(zhì)上完全相同的對(duì)象爪模,這完全違背了set的機(jī)制和作用。所以荚藻,返回內(nèi)存地址作為哈希值并不是一個(gè)好主意屋灌。
上面說過,根據(jù)約定:如果兩個(gè)對(duì)象相等应狱,則其哈希值也相等共郭,但是如果兩個(gè)哈希值相等,則對(duì)象未必相等疾呻。所以我們可以這么實(shí)現(xiàn)hash方法:
- (NSUInteger)hash {
return 666;
}
這么寫顯然符合約定除嘹,即相等的對(duì)象hash值相等,相等hash值的對(duì)象未必相等岸蜗。但是在set中大量使用這種對(duì)象將會(huì)產(chǎn)生性能問題尉咕。因?yàn)閟et在檢索哈希表時(shí),會(huì)用對(duì)象的哈希值作為索引璃岳。set會(huì)根據(jù)哈希值把對(duì)象分組年缎。在向set中添加新對(duì)象時(shí),要根據(jù)待插入的新對(duì)象的哈希值找到與之相關(guān)的那個(gè)組铃慷。然后依次檢查各個(gè)元素(調(diào)用isEqual:方法)单芜,看待插入的對(duì)象是否和數(shù)組中的某個(gè)元素相等,如果相等犁柜,那么就說明待添加的對(duì)象已經(jīng)在set中存在洲鸠。由此可知,如果令每個(gè)對(duì)象都返回相同的哈希值馋缅,那么在set中有1000000個(gè)對(duì)象的情況下扒腕,若是繼續(xù)想其中添加對(duì)象绢淀,則需要將這1000000個(gè)對(duì)象全部遍歷一遍。這樣一來就喪失了hash值的作用袜匿,把set變成了一個(gè)活生生的array更啄。
稍微好一點(diǎn)的方法
- (NSUInteger)hash {
NSString *stringToHash = [NSString stringWithFormat:@"%@,%@,%ld",self.firstName,self.lastName,self.age];
return [stringToHash hash];
}
這次所使用的方法是將NSString對(duì)象中的屬性拼接成一個(gè)新的字符串,然后另該字符串調(diào)用hash方法居灯,返回該字符串的哈希值作為這個(gè)對(duì)象的哈希值祭务。這么做符合約定,因?yàn)閮蓚€(gè)相等的對(duì)象總是會(huì)返回相同的哈希值怪嫌。但是這樣做還需要負(fù)擔(dān)創(chuàng)建一個(gè)新字符串的額外的開銷义锥,所以比返回一個(gè)單一值慢。相對(duì)而言岩灭,把這種對(duì)象添加到collection中拌倍,也會(huì)產(chǎn)生性能問題。
更加優(yōu)秀的方法
分別計(jì)算每個(gè)屬性的哈希值噪径,然后對(duì)哈希值進(jìn)行按位異或運(yùn)算柱恤,的出的結(jié)果作為對(duì)象的哈希值。
- (NSUInteger)hash {
NSInteger firstNameHash = [self.firstName hash];
NSInteger lastNameHash = [self.lastName hash];
NSInteger ageHash = self.age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
對(duì)哈希值進(jìn)行按位異或操作找爱,這種方式既能保持較高的效率梗顺,又能使生成的哈希值至少位于一定范圍之內(nèi),而不會(huì)過于頻繁的重復(fù)车摄。當(dāng)然寺谤,此算法的哈希值還是會(huì)生成碰撞,不過至少可以保證哈希值有多重可能的取值吮播,編寫hash方法時(shí)变屁,應(yīng)該用當(dāng)前的對(duì)象多做做實(shí)驗(yàn),以便在減少碰撞頻度與降低運(yùn)算復(fù)雜程度之間取舍意狠。
3.Hash方法調(diào)用時(shí)機(jī)
當(dāng)把一個(gè)對(duì)象添加到set時(shí)會(huì)調(diào)用這個(gè)對(duì)象的hash方法粟关。或者把一個(gè)對(duì)象作為key添加到dictionary中時(shí)摄职,也會(huì)調(diào)用這個(gè)對(duì)象的hash方法誊役。因?yàn)閐ictionary在查找某個(gè)value時(shí),也是根據(jù)key的hash值來提高查詢效率谷市。
當(dāng)然,如果我們把對(duì)象作為value添加到dictionary中击孩,并不會(huì)調(diào)用對(duì)象的hash方法迫悠。
注意:
如果一個(gè)自定義對(duì)象作為dictionary的key,切記要實(shí)現(xiàn)NSCopying協(xié)議中的- (id)copyWithZone:(nullable NSZone *)zone
方法巩梢。如下:
- (id)copyWithZone:(nullable NSZone *)zone {
EOCPerson *copy = [[EOCPerson alloc] init];
copy.lastName = self.lastName;
copy.firstName = self.firstName;
copy.age = self.age;
return copy;
}
4.Hash方法與isEqual:的關(guān)系
拿set為例创泄,為了優(yōu)化插入效率艺玲,當(dāng)在set中插入某個(gè)對(duì)象時(shí),首先會(huì)調(diào)用待插入對(duì)象的hash方法鞠抑,根據(jù)返回的hash值查找hash table饭聚。如果待插入對(duì)象的hash值和set中的對(duì)象的hash值都不相等。則認(rèn)為set中不存在和待插入對(duì)象相等的對(duì)象搁拙,那么就可以把待插入的對(duì)象插入到set中秒梳。如果set中存在一個(gè)對(duì)象的hash值和待插入對(duì)象的hash值相等,則再調(diào)用對(duì)象的isEqual:
方法箕速,進(jìn)行對(duì)象的判等酪碘,如果經(jīng)過isEqual:方法返回YES,則認(rèn)為兩個(gè)對(duì)象相等盐茎,即set中已經(jīng)存在一個(gè)和待插入對(duì)象相等的對(duì)象兴垦,待插入的對(duì)象不能插入到set中。否則繼續(xù)上面的操作字柠。
isEqual:調(diào)用時(shí)機(jī)
- 當(dāng)手動(dòng)調(diào)用isEqual:方法探越,對(duì)兩個(gè)對(duì)象進(jìn)行顯式的比較時(shí)。
- 當(dāng)把一個(gè)對(duì)象添加到一個(gè)成員count不為0的set中窑业,且待插入的對(duì)象的hash值和set中的成員的hash值相等的情況下钦幔,才會(huì)調(diào)用isEqual:方法。即数冬,首先調(diào)用hash方法节槐,然后才有可能調(diào)用isEqual:方法拐纱。
等同性判定的執(zhí)行深度
創(chuàng)建等同性判定方法時(shí),需啊喲決定是根據(jù)某個(gè)對(duì)象來判斷等同性揍庄,還是僅根據(jù)其中的某個(gè)或者某幾個(gè)屬性來判斷,這個(gè)取決于業(yè)務(wù)場景东抹。NSArray的檢測方式為:先看兩個(gè)數(shù)組所含對(duì)象個(gè)數(shù)是否等蚂子,若想等缭黔,則在每個(gè)對(duì)應(yīng)位置的兩個(gè)對(duì)象身上調(diào)用“isEqual:”方法。如果對(duì)應(yīng)位置上的對(duì)象均相等馏谨,那么這兩個(gè)數(shù)組相等别渔,這叫做“深度等同性判定”。不過有時(shí)無需將所有數(shù)據(jù)逐個(gè)比較哎媚,只根據(jù)其中部分?jǐn)?shù)據(jù)即可判斷二者是否相同。比如某個(gè)Person類總有一個(gè)identity字段代表身份證號(hào)碼拨与,在不存在臟數(shù)據(jù)的情況下买喧,完全可以僅憑這個(gè)identity字段判斷兩個(gè)對(duì)象是否是相同的。
不要向set中添加可變的對(duì)象
不要向set中添加可變的對(duì)象岗喉。確切的說,如果向set中添加了可變對(duì)象荚斯,那么盡量保證這個(gè)可變對(duì)象不再改變查牌。為什么呢?我們已經(jīng)了解兽泣,set和dictionary是通過哈希值檢索元素的胁孙,我們已經(jīng)說過,set火把各個(gè)對(duì)象按照其哈希值進(jìn)行分組稠鼻,如果某個(gè)可變對(duì)象在set中被分組后哈希值又改變了狂票,那么這個(gè)對(duì)象現(xiàn)在所在的組就不再合適了。要想解決這個(gè)問題闺属,我們需要確保被添加到set中的對(duì)象是不可變的或者確保可變對(duì)象被添加到set后就不再改變亚皂,或者這個(gè)對(duì)象的hash值的計(jì)算不受可變部分的影響国瓮,即匠楚,這個(gè)對(duì)象的hash值不是根據(jù)其可變部分計(jì)算出來的。
// 重寫isEqual:
- (BOOL)isEqual:(id)object{
// return [super isEqual:object];
// 一般來說峡懈,如果兩個(gè)對(duì)象的類型完全不同肪康,則肯定不等。
if ([self class] != [object class]) {
return NO;
}
// 兩個(gè)對(duì)象指針相等谒撼,其指向同一塊內(nèi)存雾狈,則肯定相等。
if (self == object) {
return YES;
}
EOCPerson *otherPerson = (EOCPerson *)object;
// 兩個(gè)對(duì)象相應(yīng)的屬性如果不等辩蛋,則也認(rèn)為不等移盆。
if (![self.firstName isEqualToString:otherPerson.firstName]){
return NO;
}
if (![self.lastName isEqualToString:otherPerson.lastName]){
return NO;
}
if (self.age != otherPerson.age) {
return NO;
}
// 如果屬性也相等咒循,則認(rèn)為相等。
return YES;
}
// 重寫hash
- (NSUInteger)hash {
NSLog(@"%s",__func__);
NSInteger firstNameHash = [self.firstName hash];
NSInteger lastNameHash = [self.lastName hash];
NSInteger ageHash = self.age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
// 調(diào)用
EOCPerson *p1 = [[EOCPerson alloc] init];
p1.firstName = @"VV";
p1.lastName = @"S";
EOCPerson *p2 = [[EOCPerson alloc] init];
p2.firstName = @"VV";
p2.lastName = @"S";
NSMutableSet *setM = [NSMutableSet set];
[setM addObject:p1];
[setM addObject:p2];
NSLog(@"set count == %ld",[setM count]); // set count == 1
上面我們看到颖医,向set中添加兩個(gè)相同的對(duì)象蚁署,firstName和lastName值完全相同,打印set中元素的個(gè)數(shù)哪痰,其打印結(jié)果為1久妆。這樣完全符合set能夠去重的功能筷弦。但是抑诸,如果我們繼續(xù)添加一個(gè)不同于p1和p2的p3對(duì)象爹殊,然后改變p3的各個(gè)屬性和p1相同,再觀察set count层玲,如下:
EOCPerson *p1 = [[EOCPerson alloc] init];
p1.firstName = @"VV";
p1.lastName = @"S";
EOCPerson *p2 = [[EOCPerson alloc] init];
p2.firstName = @"VV";
p2.lastName = @"S";
NSMutableSet *setM = [NSMutableSet set];
[setM addObject:p1];
[setM addObject:p2];
NSLog(@"set count == %ld",[setM count]); // set count == 1
EOCPerson *p3 = [[EOCPerson alloc] init];
p3.firstName = @"VV";
[setM addObject:p3];
NSLog(@"set count == %ld",[setM count]); // set count == 2
p3.lastName = @"S";
NSLog(@"set count == %ld",[setM count]); // set count == 2
我們看到辛块,上面給setM對(duì)象添加了一個(gè)p3后铅碍,其count == 2,這樣是ok的尘盼。但是把p3的lastName改為和p1的lastName相同時(shí)呜魄,set count 仍然為2爵嗅。此時(shí)set中竟然出現(xiàn)了兩個(gè)完全相同的對(duì)象!這完全違背了set的本意趟庄,因?yàn)閟et的作用就是去重伪很,根據(jù)set的語義,set中是不會(huì)也不應(yīng)該出現(xiàn)了兩個(gè)完全相同的對(duì)象猫十。
如果把這個(gè)setM對(duì)象在拷貝一下呆盖,情況更糟了:
NSSet *s = [setM copy];
NSLog(@"set count == %ld",[s count]); // set count == 1
你會(huì)發(fā)現(xiàn),s對(duì)象雖然是setM的副本宙项,但是s.count卻是1株扛。此s對(duì)象看上去像是由一個(gè)空set開始,通過把setM中的對(duì)象添加到s中而創(chuàng)建出來的盆繁。無論如何改基,這樣做已經(jīng)存在了很大的風(fēng)險(xiǎn),這可能給我們的程序調(diào)試帶來無法想象的難度。
舉這個(gè)例子是想說明:把某個(gè)對(duì)象放入set這種集合對(duì)象中躁染,就不宜改變其內(nèi)容吞彤。
總結(jié)
- 把某個(gè)對(duì)象添加到set中時(shí),都會(huì)調(diào)用這個(gè)對(duì)象的hash方法計(jì)算hash值挠羔。
- 把一個(gè)對(duì)象添加到set中時(shí)埋嵌,如果set中不存在任何元素,這個(gè)對(duì)象會(huì)被直接添加到set中范舀。相反了罪。如果set中存在元素泊藕,那么待添加的對(duì)象的hash值會(huì)和set中的每個(gè)元素的hash值進(jìn)行比較。如果不等玫锋,會(huì)繼續(xù)和set中的下一個(gè)元素比較hash值踊餐,直到待添加的對(duì)象的hash值和set中所有元素的hash值比較完畢為止。
- 如果set中存在一個(gè)元素的hash值和待添加的對(duì)象的hash值相等三痰,那么待插入的對(duì)象會(huì)調(diào)用自己的isEqual:方法散劫,以set中的元素為參數(shù),進(jìn)行比較获搏,如果isEqual:返回YES常熙,證明這兩個(gè)對(duì)象相同,那么待插入的對(duì)象不會(huì)插入到set中仿贬。如果isEqual:返回NO墓贿,證明這兩個(gè)對(duì)象不同,對(duì)象可以插入到set中队伟。
- hash的默認(rèn)實(shí)現(xiàn)是返回對(duì)象的指針地址嗜侮。isEqual:的默認(rèn)實(shí)現(xiàn)是比較兩個(gè)對(duì)象的指針地址代嗤。
- 相同的對(duì)象必須具有相同的hash值,但是兩個(gè)hash值相等的對(duì)象未必相同宜猜。
- 若想檢測對(duì)象的等同性硝逢,需要提供“isEqual:”與hash方法渠鸽。
- 根據(jù)實(shí)際也需重寫isEqual:方法,不要盲目檢查每條屬性憨奸,而是應(yīng)該按照具體需求來指定檢查方案凿试。
- 最好不要把可變對(duì)象添加到set中似芝,最好也請(qǐng)不要改變set中某個(gè)元素党瓮,否則容易產(chǎn)生想象不到的錯(cuò)誤盐类,也會(huì)增加調(diào)試的難度。
- hash方法應(yīng)該使用計(jì)算速度快而且哈希值碰撞幾率低的算法枪萄。一般情況下硬毕,建議使用按位異或操作吐咳。
最后元践,借用大神“Mattt Thompson”的話:
經(jīng)過這么多的解釋,希望我們在這個(gè)有些詭譎的話題上取得了”相同“的認(rèn)識(shí)沪羔。 作為人類蔫饰,我們很努力地去理解和實(shí)現(xiàn)平等愉豺,在我們的社會(huì)中,在自然生態(tài)環(huán)境中杖剪,在立法和執(zhí)法中驰贷,在選舉我們領(lǐng)導(dǎo)人的過程中括袒,在人類作為一個(gè)物種互相溝通延續(xù)我們的存在這一共識(shí)中。愿我們能繼續(xù)這個(gè)奮斗的過程芥炭,最終達(dá)到理想的彼岸,在那里米苹,評(píng)價(jià)一個(gè)人的標(biāo)準(zhǔn)是他的人格蘸嘶,就像我們判斷一個(gè)變量是通過它的內(nèi)存地址一樣
陪汽。
文/VV木公子(簡書作者)
PS:如非特別說明,所有文章均為原創(chuàng)作品况增,著作權(quán)歸作者所有训挡,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)澜薄,并注明出處,所有打賞均歸本人所有颊艳!
如果您是iOS開發(fā)者忘分,或者對(duì)本篇文章感興趣妒峦,請(qǐng)關(guān)注本人,后續(xù)會(huì)更新更多相關(guān)文章绸狐!敬請(qǐng)期待累盗!
參考文章
iOS開發(fā) 之 不要告訴我你真的懂isEqual與hash!
Equality(翻譯)
Equality(英文)
isEqual & hash