淺析對(duì)象等同性判斷

前言

對(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基類的自定義類,我們?cè)撛趺磁袛嗟韧阅??不用?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é)

  1. 把某個(gè)對(duì)象添加到set中時(shí),都會(huì)調(diào)用這個(gè)對(duì)象的hash方法計(jì)算hash值。
  2. 把一個(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值比較完畢為止。
  1. 如果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中。
  2. hash的默認(rèn)實(shí)現(xiàn)是返回對(duì)象的指針地址。isEqual:的默認(rèn)實(shí)現(xiàn)是比較兩個(gè)對(duì)象的指針地址。
  3. 相同的對(duì)象必須具有相同的hash值,但是兩個(gè)hash值相等的對(duì)象未必相同。
  4. 若想檢測對(duì)象的等同性,需要提供“isEqual:”與hash方法。
  5. 根據(jù)實(shí)際也需重寫isEqual:方法,不要盲目檢查每條屬性,而是應(yīng)該按照具體需求來指定檢查方案。
  6. 最好不要把可變對(duì)象添加到set中,最好也請(qǐng)不要改變set中某個(gè)元素,否則容易產(chǎn)生想象不到的錯(cuò)誤,也會(huì)增加調(diào)試的難度。
  7. hash方法應(yīng)該使用計(jì)算速度快而且哈希值碰撞幾率低的算法。一般情況下,建議使用按位異或操作。

最后,借用大神“Mattt Thompson”的話:
經(jīng)過這么多的解釋,希望我們?cè)谶@個(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容