在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
從上面的例子可以看出,由于s1
和s2
不是同一個對象(即對象地址不同),所以==
結果為0
(NO
),而isEqual
和isEqualToString
方法則判斷對象是否相同,所以結果為:1
、1
。
如何重寫isEqual方法
NSObject
協議中有兩個方法用于判斷等同性
:
- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
這兩個方法的默認實現是:當且僅當其“指針值”完全相同時,這兩個對象才相等。如果isEqual
判斷兩個對象相等,那么其hash
值一定相等;反之,如果兩個對象的hash
值相等,則對象不一定相等(原因:hash
值的獲取方式可能造成沖突,導致盡管hash
的key
值不同,但是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
和設置為NSDictionary
的key
時才會被調用,這是因為NSSet
需要根據hash
值來快速查找成員,而NSDictionary
在查找key
時,也利用key
的hash
查找來提高查找效率。
hash方法與判等的關系?
hash
是對象等同性
的必要非充分條件,在NSSet
和NSDictionary
中判斷時,會先判斷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