NSObject子類重寫isEqual:函數(shù)和hash函數(shù)實(shí)踐

本體性 和 相等性:(摘自Equality)

相等性:當(dāng)兩個(gè)物體有一系列相同的可觀測的屬性時(shí)鹅髓,兩個(gè)物體可能是互相相等或者等價(jià)的跃捣。但這兩個(gè)物體仍然是不同的险掀,他們各自有自己的本體嗜价。
本體性:在編程中腋妙,一個(gè)對象的本體和它的內(nèi)存地址是相互關(guān)聯(lián)的默怨。關(guān)聯(lián)的內(nèi)存地址相同則具有本體性。

對象比較:

比較方式:
1骤素、==:對于基本數(shù)據(jù)類型比較的是值匙睹,對于對象則是本體比較愚屁,也就是直接比較對象的指針地址
2、isEqual:
有了==后痕檬,為什么還要有 isEqual:,這里要搞清楚兩個(gè)概念:
對象比較和對象地址比較:
這里也就回到了文章開始提到的相等性霎槐,有時(shí)我們比較對象,并不是為了比較對象的地址是否相同梦谜,而是只要是對象的屬性丘跌,內(nèi)容等相同我們就會(huì)認(rèn)為對象相同。(這也是為什么我們會(huì)自定義isEqual:函數(shù)
OC對象比較一般來說是比較“本體”唁桩,而本體的比較比的是對象的內(nèi)存地址闭树,(即:只要是地址相同,則被認(rèn)為是相同的對象)但是當(dāng)我們重寫了isEqual:后荒澡,動(dòng)機(jī)就是為了做相等性比較报辱。

重寫hash函數(shù)

哈希表的查找原理:

說到hash我們先來簡單了解下Hash Table這種數(shù)據(jù)結(jié)構(gòu):
1、數(shù)組中查找一個(gè)元素的過程:
1)遍歷整個(gè)數(shù)組单山、
2)取出數(shù)組中每一個(gè)值碍现,并將取出的值同目標(biāo)值進(jìn)行比較。若一致則返回該成員米奸。
如果數(shù)組未經(jīng)過排序昼接,查找的時(shí)間復(fù)雜度是O(length).
2、而當(dāng)將一個(gè)元素加入到Hash Table中時(shí)悴晰,會(huì)給這個(gè)元素分配一個(gè)hash值慢睡,用來表示這個(gè)元素在hash表中的位置。(hash值的生成就是通過hash函數(shù))膨疏。
通過位置標(biāo)識一睁,hash表的查找時(shí)間復(fù)雜度為O(1)钻弄。但是**多個(gè)成員的hash值相同時(shí)即:出現(xiàn)hash沖突佃却。這是時(shí)間復(fù)雜度就會(huì)降低。過程總結(jié)如下:
1)通過hash值定位到元素所在的位置
2)如果該位置有多個(gè)hash值相同的成員窘俺,則對該位置上的hash值相同的元素以數(shù)組方式進(jìn)行查找饲帅。
通常為了避免情況2的出現(xiàn),有一個(gè)規(guī)范:加入到hash表中的元素應(yīng)盡量保證其hash值唯一瘤泪。

iOS中關(guān)于hash方法的重寫:

3灶泵、iOS中NSSet、NSDictionary都是基于hash table實(shí)現(xiàn)的对途。所以當(dāng)我們自定義的類重寫了isEqual方法赦邻,且該對象有可能被加入到集合中時(shí),要保證重寫hash方法实檀。
原因如下:
1惶洲、為了保證效率按声,基于散列表實(shí)現(xiàn)的NSSet、NSDictionary在對成員判斷是否相等時(shí)恬吕,會(huì):
1)想判斷連個(gè)對象的hash值是否相同签则,如果相同則進(jìn)行第二步處理,反之铐料,判定為不相等渐裂。
2)在基于第一步的條件下,再調(diào)用isEqual:(isEqualXXX:)來進(jìn)行判斷钠惩。
也就是說:hash值相同柒凉,對象也有可能不相同。但是我們一般約定:如果對象相等篓跛,hash值一定要保證相等扛拨。

2、既然重寫了isEqual:函數(shù)举塔,說明我們想要做的是“相等性”比較绑警,而不是“本體性”比較,而默認(rèn)的hash函數(shù)返回值則是對象的內(nèi)存地址央渣。既然是做“相等性”比較计盒,那就應(yīng)該讓hash返回值也符合“相等性”比較行為,而不是返回對象內(nèi)存地址芽丹。

來看一段代碼:

person.m文件

@implementation person

- (instancetype)initWithUserName:(NSString *)userName {
    self = [super init];
    if(self) {
        self.userName = userName;
    }
    return self;
}

- (BOOL)isEqual:(id)object {
    NSLog(@"===isEqual:self:%@,object:%@",self,object);
//    return [super isEqual:object];
    if(self == object) {
        return YES;
    }
    else {
        if([self.userName isEqualToString:((person *)object).userName]) {
            return YES;
        }
        return NO;
    }
}

- (NSUInteger)hash {
    NSLog(@"=====hash");
    return [super hash];
}

//重寫后直接調(diào)super和不重寫hash方法作用是一致的北启。

otherClass.m
person *pp = [[person alloc] initWithUserName:@"1111"];
person *pp11 = [[person alloc] initWithUserName:@"1111"];
NSMutableSet *set = [NSMutableSet set];
[set addObject:pp];
[set addObject:pp11];

NSLog(@"=====%@",set);

期望輸出:set中置于一個(gè)元素,因?yàn)槲覀冎囟x了isEqual拔第,只要是userName相同咕村,我們就認(rèn)為對象是相同的。所以pp和pp11在這里是相等的蚊俺,不應(yīng)該被他添加到集合中懈涛。
實(shí)際輸出:2個(gè)元素都被加入了集合中。
原因分析:在添加第二個(gè)元素時(shí)泳猬,因?yàn)閔ash值返回的是每個(gè)對象的內(nèi)存地址批钠,所以被判斷為不相等,沒有執(zhí)行isEqual:函數(shù)得封。幸而直接被添加進(jìn)set中埋心。

疑問:如果把上面的代碼作如下改動(dòng):

//添加代碼
person *pp22 = [[person alloc] initWithUserName:@"1111"];
[set addObject:pp22];
NSLog(@"=====%@",set);

會(huì)發(fā)現(xiàn)pp22沒有被添加到集合中,打印pp22 的hash值發(fā)現(xiàn)同pp忙上、pp11不相同,而且這里卻執(zhí)行了isEqual:函數(shù)拷呆。所以不明白為什么沒有添加進(jìn)去? 如果有同學(xué)有好的理解,請?jiān)谠u論區(qū)跟我分享茬斧。

如何重寫hash函數(shù):

直接說結(jié)論:

將對象關(guān)鍵屬性的hash值進(jìn)行位或運(yùn)算箫柳,將運(yùn)算結(jié)果作為對象的hash值。

這里只是提供了一種還算不錯(cuò)的實(shí)現(xiàn)方式啥供,諸多開源庫其實(shí)都有很好的實(shí)踐悯恍。大家可自行參閱。

代碼示例:

- (NSUInteger)hash {
    return [self.userName hash] ^ [self.lastName hash];
}

hash的設(shè)計(jì)是為了快速查找,要盡可能的避免hash沖突,也就是不滿足isEqueal的兩個(gè)元素,盡量hash不相等,在設(shè)計(jì)hash的時(shí)候要考慮,是否會(huì)比較輕易的出現(xiàn)兩個(gè)不等的對象hash值相等的情況伙狐。如果是涮毫,那就需要重新設(shè)計(jì)hash函數(shù)的實(shí)現(xiàn)。
以上面的實(shí)現(xiàn)為例贷屎。(例如有人曾給出這個(gè)例子)john smith 和 smith john結(jié)果是一樣的罢防。
所以這里比較好的實(shí)踐為:

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

添加進(jìn)集合后,保證對象的hash值不可變:

如果重寫了對象的hash函數(shù)唉侄,而且把對象作為 基于“哈希表”實(shí)現(xiàn)的集合(NSSet咒吐、NSDictionary、NSMapTable属划、NSHashTable)中的key時(shí)恬叹,需要保證在集合內(nèi)的期間,對象的hash不變同眯。
看下面代碼具體解釋下:

NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:@"hhhhh" forKey:pp];
NSLog(@"====%@",[dic objectForKey:pp]);

結(jié)果:輸出hhh
但是我們稍加改造绽昼,如下:

    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    [dic setObject:@"hhhhh" forKey:pp];
    NSLog(@"====%@",[dic objectForKey:pp]);
    pp.userName = @"test";
    NSLog(@"====%@",[dic objectForKey:pp]);
結(jié)果:
 TestIsE&Hash[7399:1002659] ====hhhhh
 TestIsE&Hash[7399:1002659] ====(null)

當(dāng)對象在集合內(nèi)期間,如果改變了對象的hash值须蜗,會(huì)導(dǎo)致hash表結(jié)構(gòu)的結(jié)合無法正確查找的問題硅确。

添加isEqualXXX:函數(shù):

NSObject子類重寫了isEqual:后,需要做一下三方面的工作:

1明肮、實(shí)現(xiàn)一個(gè)新的 isEqualTo__ClassName__ 方法菱农,進(jìn)行實(shí)際意義上的值的比較。
2柿估、重載 isEqual: 方法進(jìn)行類和對象的本體性檢查循未,如果失敗則回退到上面提到的值(相等性)比較方法。
3官份、重載 hash 方法只厘。

參考:
isEqual & hash
不懂isEqual
解析和重寫NSObjetc的isEqual和Hash
Equality

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舅巷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌河咽,老刑警劉巖钠右,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忘蟹,居然都是意外死亡飒房,警方通過查閱死者的電腦和手機(jī)搁凸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狠毯,“玉大人护糖,你說我怎么就攤上這事〗浪桑” “怎么了嫡良?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長献酗。 經(jīng)常有香客問我寝受,道長,這世上最難降的妖魔是什么罕偎? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任很澄,我火速辦了婚禮,結(jié)果婚禮上颜及,老公的妹妹穿的比我還像新娘甩苛。我一直安慰自己,他們只是感情好俏站,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布浪藻。 她就那樣靜靜地躺著,像睡著了一般乾翔。 火紅的嫁衣襯著肌膚如雪爱葵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天反浓,我揣著相機(jī)與錄音萌丈,去河邊找鬼。 笑死雷则,一個(gè)胖子當(dāng)著我的面吹牛辆雾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播月劈,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼度迂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了猜揪?” 一聲冷哼從身側(cè)響起惭墓,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎而姐,沒想到半個(gè)月后腊凶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年钧萍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了褐缠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡风瘦,死狀恐怖队魏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情万搔,我是刑警寧澤胡桨,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蟹略,受9級特大地震影響登失,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挖炬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一揽浙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧意敛,春花似錦馅巷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至撩独,卻和暖如春敞曹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背综膀。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工澳迫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剧劝。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓橄登,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讥此。 傳聞我的和親對象是個(gè)殘疾皇子拢锹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353