如何理解iOS的“對象等同性”

iOS開發過程中,我們經常需要用到等同性來判斷兩個對象是否相等,通常我們會使用==來判斷,但是這樣比較出來的結果可能不是我們期望的;所以,一般我們會使用NSObject協議聲明的isEqual方法來判斷對象的等同性。並且,爲了更好的進行深層次的比較,iOS系統中的NSObject子類還實現了各自的isEqual:方法。

== 究竟比較的是什麼?

對於基本類型,==比較的是值;對於對象類型,`==“比較的是對象的地址,即是否爲同一個對象

    NSString *s1 = @"123";
    NSString *s2 = [NSString stringWithFormat:@"%d", 123];

    BOOL e1 = s1 == s2;
    BOOL e2 = [s1 isEqual:s2];
    BOOL e3 = [s1 isEqualToString:s2];

    NSLog(@"%d, %d, %d", e1, e2, e3); // 0, 1, 1

從上面的例子可以看出,由於s1s2不是同一個對象(即對象地址不同),所以==結果爲0NO),而isEqualisEqualToString方法則判斷對象是否相同,所以結果爲:11

如何重寫isEqual方法

NSObject協議中有兩個方法用於判斷等同性

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;

這兩個方法的默認實現是:當且僅當其“指針值”完全相同時,這兩個對象才相等。如果isEqual判斷兩個對象相等,那麼其hash值一定相等;反之,如果兩個對象的hash值相等,則對象不一定相等(原因:hash值的獲取方式可能造成衝突,導致儘管hashkey值不同,但是hash值是一樣)。

iOS系統已經實現了部分NSObject子類的isEqual方法(更多參考Equality),如:

1. NSString - isEqualToString

2. NSArray - isEqualToArray

3. NSDictionary - isEqualToDictionary

4. NSSet - isEqualToSet

但是對於自定義的類型來說,如果有對象等同性的比較需求,那麼需要自行實現isEqual方法,具體步驟如下:

.1 實現一個isEqualTo__ClassName__: 方法來執行有意義的值比較

.2 重寫isEqual:方法來作類型和對象等同性檢查, 回調上述的值比較方法

.3 重寫 hash, 在集合中查找時最先調用

實例代碼:

Person頭文件

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;

@end

Person實現isEqual方法

- (BOOL)isEqual:(id)object {
    if (self == object) {
        return YES;
    }

    if (![object isKindOfClass:[Person class]]) {
        return NO;
    }

    return [self isEqualToPerson:(Person *)object];
}

- (BOOL)isEqualToPerson:(Person *)person {
    if (!person) {
        return NO;
    }

    BOOL haveEqualNames = (!self.name && !person.name) || [self.name isEqualToString:person.name];
    BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];

    return haveEqualNames && haveEqualBirthdays;
}

上述代碼實現了比較自定義對象的等同性,也適用於具有繼承關係的場景

實現hash方法

在實現hash方法之前,需要先了解一下hash表是什麼:hash表也稱散列表,或哈希表,hash表是一種特殊的數據結構,它同數組、鏈表以及二叉排序樹等相比較有很明顯的區別,它能夠快速定位到想要查找的記錄,而不是與表中存在的記錄的關鍵字進行比較來進行查找。這個源於hash表設計的特殊性,它採用了函數映射的思想將記錄的存儲位置與記錄的關鍵字關聯起來,從而能夠很快速地進行查找。

本質來講,就是把所有成員的固定值(也可以是轉換後的固定值)通過f(x)映射後形成的一張表,然後在需要查找成員時,就直接利用hash表來找,不需要順序查找或鏈式查找,速度最快可以達到O(1)的級別,是典型的空間換時間的做法。

hash方法只有在被添加到NSSet和設置爲NSDictionarykey時纔會被調用,這是因爲NSSet需要根據hash值來快速查找成員,而NSDictionary在查找key時,也利用keyhash查找來提高查找效率。

hash方法與判等的關係?

hash是對象等同性的必要非充分條件,在NSSetNSDictionary中判斷時,會先判斷hash值是否相等,如果相等,那麼就會進行isEqual的判斷;反之,不相等,直接判斷對象不相等

重寫hash方法:

- (NSUInteger)hash {
    return [self.name hash] ^ [self.birthday hash];

    // NSObject的hash值是調用hash方法的對象地址,一般不用,需要重寫一個hash的方法實現
    //return [super hash]; 
}

上述是hash值的一種實現方式,用屬性的XOR方式來設置唯一的hash值,可以滿足絕大多數的場景需求。其中,hash值的實現有多種不同的方式,能夠產生唯一性的hash值的概率越高,表明hash的可靠性越高。一般情況下,hash值都是唯一的,利於快速查找;但是,如果hash值出現相等的情況,即出現衝突,那麼就需要特殊處理,具體的處理方法請參考文末的資料。


參考資料

iOS判斷對象相等 重寫isEqual、isEqualToClass、hash

iOS開發 之 不要告訴我你真的懂isEqual與hash!

Hash算法

Hash表

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章