前言
NSObject
給我們提供了-isEqual
和-hash
方法,下面我們具體介紹一下這兩個方法主要功能是什么,會在什么時候被調(diào)用,如何根據(jù)自己定制化的需求進(jìn)行重寫.
isEqual
我們先查看一下方法的聲明- (BOOL)isEqual:(id)object;
,拿另一個對象與當(dāng)前object
進(jìn)行對比,返回一個布爾值,來確認(rèn)這兩個對象是否相等
.
在進(jìn)一步解釋這個方法之前,我們先看一下相等
的定義.
什么是相等
我們知道==
運算符和isEqual
都可以用來判斷相等
,他們有什么區(qū)別呢?
我們下面對相等
進(jìn)行一些定義,在不同的條件下,我們對相等
的定義也會發(fā)生變化,大致分為以下幾種
- 內(nèi)存地址相等
- 自定義的某些屬性相等
內(nèi)存地址相等,是說這是兩個完全相等的對象
某些屬性相等,這是需要我們關(guān)注和擴(kuò)展的部分,自定義相等
的條件
系統(tǒng)有哪些自定義的相等
-
NSAttributedString
-isEqualToAttributedString:
-
NSData
-isEqualToData:
-
NSDate
-isEqualToDate:
-
NSDictionary
-isEqualToDictionary:
-
NSHashTable
-isEqualToHashTable:
-
NSIndexSet
-isEqualToIndexSet:
-
NSNumber
-isEqualToNumber:
-
NSOrderedSet
-isEqualToOrderedSet:
-
NSSet
-isEqualToSet:
-
NSString
-isEqualToString:
-
NSTimeZone
-isEqualToTimeZone:
-
NSValue
-isEqualToValue:
如何自定義-isEqual
假設(shè)我們有一個Person
類
@interface Person
@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *secondName
@property(nonatomic, strong) NSDate *birthday;
@end
我們先自定義一下相等
的概念,這里我們舉例,如果firstName
和secondName
相等,就視為person
相等.
下面要做的就是先增加自定義的相等方法:- (BOOL)isEqualToPerson:(Person *)person;
在.m
的實現(xiàn)如下
@implementation Person
- (BOOL)isEqualToPerson:(Person *)person {
if (!person) {
return NO;
}
BOOL isFirstNameEqual = (!self.firstName && !person.firstName) || [self.firstName isEqualToString:person.firstName];
BOOL isSecondNameEqual = (!self.secondName && !person.secondName) || [self.secondName isEqualToString:person.secondName];
return isFirstNameEqual && isSecondNameEqual;
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[Person class]]) {
return NO;
}
return [self isEqualToPerson:(Person *)object];
}
- (NSUInteger)hash {
return [self.firstName hash << 8] ^ [self.secondName hash];
}
下面分步解析一下:
- 重寫父類的
isEqual
方法,首先判斷是否內(nèi)存地址相等self == object
- 判斷
![object isKindOfClass:[Person class]]
如果Class
不相等則直接返回NO
- 調(diào)用我們自定義的判等方法
- (BOOL)isEqualToPerson:(Person *)person
.
isEqual
什么時候會被調(diào)用
- 我們可以直接調(diào)用
isEqual
方法來判斷兩個對象是否相等 -
NSArray
的containObject:
方法,會遍歷數(shù)組的元素,并通過isEqual
來判斷是否相等 -
NSSet
的containObject:
方法,會先調(diào)用-hash
,如果-hash
不相等,直接返回false,如果hash
相等,則會再調(diào)用isEqual
說到這里問題來了,什么是-hash
方法,它的作用是什么?
hash
方法
- (NSUInteger)hash
返回一個整數(shù),這個數(shù)代表的就是當(dāng)前對象的哈希值
有一個很重要的規(guī)范 : 如果兩個對象相等
,他們的hash
值必須相等, 如果某個類自定義了isEqual
方法,并且這個類的實例有可能會被加入到集合中,一點要確保hash
方法被重新定義
和數(shù)組把元素存儲在一系列連續(xù)的地址中不同锌妻,哈希算法使得 NSSet
和 NSDictionary
能夠非持粘椋快速地(O(1))
進(jìn)行元素查找,哈希表會在內(nèi)存中分配n
個位置呼渣,然后使用一個函數(shù)來計算出位置范圍之內(nèi)的某個具體位置.
在數(shù)組和hash
表中要判斷一個元素是不是存在的算法和效率是不一樣的凭戴,數(shù)組需要對數(shù)組中每個元素的位置都進(jìn)行檢查,hash
有一個更快速的查找方式.
一個好的 hash
函數(shù)在不需要太多計算量的情況下陷谱,可以使得生成的位置分布接近于均勻分布,當(dāng)兩個不同的對象計算出相同的散列值時鲫惶,我們稱其為發(fā)生了 哈希碰撞 鞍泉。當(dāng)出現(xiàn)碰撞時疑苫,哈希表會從碰撞產(chǎn)生的位置開始向后尋找,把新的元素放在第一個可供放置的位置,隨著哈希表變得越來越致密畔柔,發(fā)生碰撞的可能性也會隨之增加氯夷,導(dǎo)致查找可用位置花費的時間也會增加(這也是為什么我們希望哈希函數(shù)的結(jié)果分布更接近于均勻分布).
大家對于哈希碰撞
和哈希算法
有一個基本的概念就可以,這一塊之后會單獨拿出來進(jìn)行分析,敬請期待.好了,我們我們繼續(xù)針對我們上面的isEqual
需求進(jìn)行講解.
自定義-hash
方法
如果兩個對象相等
,他們的hash
值必須相等, 如果某個類自定義了isEqual
方法,并且這個類的實例有可能會被加入到集合中,一點要確保hash
方法被重新定義
自定義了兩個對象相等的規(guī)則,那么hash
要做的是保證在規(guī)則下了個對象的hash
值要相等.
我們可以通過
- (NSUInteger)hash {
//不完善的示例
return [self.firstName hash] ^ [self.secondName hash];
}
來實現(xiàn),但是為什么要對firstName
進(jìn)行位移呢?
上面我們說過,hash
的設(shè)計是為了快速查找,要盡可能的避免hash
沖突,也就是不滿足isEqueal
的兩個元素,盡量hash
不相等,在設(shè)計hash
的時候要考慮,是否會比較輕易的使得兩個不等的對象hash
值相等,如果是,那么hash
算法就要重新設(shè)計.
對于上面的示例來說,john smith
和smith john
就會有問題,雖然無法避免hash
沖突,但是不應(yīng)該這么輕易沖突,為了解決這個易見的hash
沖突,可以使用以下
- (NSUInteger)hash {
return [self.firstName hash << 8] ^ [self.secondName hash];
}
總結(jié)
通過以上的講解和示例,我們已經(jīng)可以實現(xiàn)自定義isEqual
和hash
方法了.
對于hash
方法,我們提到我們希望哈希函數(shù)的結(jié)果分布更接近于均勻分布,也就是在避免顯而易見的哈希沖突前提下,使得哈希算法在我們現(xiàn)有的范圍內(nèi)有一定的沖突,目的是為了快速查找,這一塊內(nèi)容對于本篇來說有一點超綱,如果你感興趣,可以繼續(xù)關(guān)注我之后的文章,我會針對哈希沖突
進(jìn)行一個比較全面的分析.