解析和重寫NSObject 的isEqual和hash方法

前言

NSObject給我們提供了-isEqual-hash方法,下面我們具體介紹一下這兩個方法主要功能是什么,會在什么時候被調用,如何根據自己定制化的需求進行重寫.

isEqual

我們先查看一下方法的聲明- (BOOL)isEqual:(id)object;,拿另一個對象與當前object進行對比,返回一個布爾值,來確認這兩個對象是否相等.
在進一步解釋這個方法之前,我們先看一下相等的定義.

什么是相等

我們知道==運算符和isEqual都可以用來判斷相等,他們有什么區別呢?
我們下面對相等進行一些定義,在不同的條件下,我們對相等的定義也會發生變化,大致分為以下幾種

  1. 內存地址相等
  2. 自定義的某些屬性相等

內存地址相等,是說這是兩個完全相等的對象
某些屬性相等,這是需要我們關注和擴展的部分,自定義相等的條件

系統有哪些自定義的相等
  • 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

假設我們有一個Person

@interface Person
@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *secondName
@property(nonatomic, strong) NSDate *birthday;
@end

我們先自定義一下相等的概念,這里我們舉例,如果firstNamesecondName相等,就視為person相等.
下面要做的就是先增加自定義的相等方法:- (BOOL)isEqualToPerson:(Person *)person;
.m的實現如下

@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];
}

下面分步解析一下:

  1. 重寫父類的isEqual方法,首先判斷是否內存地址相等self == object
  2. 判斷![object isKindOfClass:[Person class]]如果Class不相等則直接返回NO
  3. 調用我們自定義的判等方法- (BOOL)isEqualToPerson:(Person *)person.
isEqual什么時候會被調用
  1. 我們可以直接調用isEqual方法來判斷兩個對象是否相等
  2. NSArraycontainObject:方法,會遍歷數組的元素,并通過isEqual來判斷是否相等
  3. NSSetcontainObject:方法,會先調用-hash,如果-hash不相等,直接返回false,如果hash相等,則會再調用isEqual

說到這里問題來了,什么是-hash方法,它的作用是什么?

hash方法

- (NSUInteger)hash返回一個整數,這個數代表的就是當前對象的哈希值
有一個很重要的規范 : 如果兩個對象相等,他們的hash值必須相等, 如果某個類自定義了isEqual方法,并且這個類的實例有可能會被加入到集合中,一點要確保hash方法被重新定義

和數組把元素存儲在一系列連續的地址中不同,哈希算法使得 NSSetNSDictionary 能夠非常快速地(O(1)) 進行元素查找,哈希表會在內存中分配n個位置,然后使用一個函數來計算出位置范圍之內的某個具體位置.
在數組和hash表中要判斷一個元素是不是存在的算法和效率是不一樣的,數組需要對數組中每個元素的位置都進行檢查,hash有一個更快速的查找方式.

一個好的 hash函數在不需要太多計算量的情況下,可以使得生成的位置分布接近于均勻分布,當兩個不同的對象計算出相同的散列值時,我們稱其為發生了 哈希碰撞 。當出現碰撞時,哈希表會從碰撞產生的位置開始向后尋找,把新的元素放在第一個可供放置的位置,隨著哈希表變得越來越致密,發生碰撞的可能性也會隨之增加,導致查找可用位置花費的時間也會增加(這也是為什么我們希望哈希函數的結果分布更接近于均勻分布).
大家對于哈希碰撞哈希算法有一個基本的概念就可以,這一塊之后會單獨拿出來進行分析,敬請期待.好了,我們我們繼續針對我們上面的isEqual需求進行講解.

自定義-hash方法

如果兩個對象相等,他們的hash值必須相等, 如果某個類自定義了isEqual方法,并且這個類的實例有可能會被加入到集合中,一點要確保hash方法被重新定義
自定義了兩個對象相等的規則,那么hash要做的是保證在規則下了個對象的hash值要相等.
我們可以通過

- (NSUInteger)hash {
  //不完善的示例
  return [self.firstName hash] ^ [self.secondName hash];
}

來實現,但是為什么要對firstName進行位移呢?
上面我們說過,hash的設計是為了快速查找,要盡可能的避免hash沖突,也就是不滿足isEqueal的兩個元素,盡量hash不相等,在設計hash的時候要考慮,是否會比較輕易的使得兩個不等的對象hash值相等,如果是,那么hash算法就要重新設計.
對于上面的示例來說,john smithsmith john就會有問題,雖然無法避免hash沖突,但是不應該這么輕易沖突,為了解決這個易見的hash沖突,可以使用以下

- (NSUInteger)hash {
  return [self.firstName hash << 8] ^ [self.secondName hash];
}

總結

通過以上的講解和示例,我們已經可以實現自定義isEqualhash方法了.
對于hash方法,我們提到我們希望哈希函數的結果分布更接近于均勻分布,也就是在避免顯而易見的哈希沖突前提下,使得哈希算法在我們現有的范圍內有一定的沖突,目的是為了快速查找,這一塊內容對于本篇來說有一點超綱,如果你感興趣,可以繼續關注我之后的文章,我會針對哈希沖突進行一個比較全面的分析.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容