IsEqual與Hash個人理解
isEqual
NSObject類的實體方法: - (BOOL)isEqual:(id)object 主要是根據物件的記憶體地址來判斷兩個物件是否相等,這里與 ==效果相同,
-
isEqualToString
(BOOL)isEqualToString:(NSString *)aString 是NSString類的實體方法,它主要用于比較兩個字串中的內容是否相同,而非比較兩個字串所在記憶體地址,該方法常用,
-
自定義類的isEqual
在開發需求中,如果有比較兩個類物件是否具有等同性時,通常會根據需求來對父類的isEqual進行改寫,這里的做法通常是:
-
先創建自定義的等同性判斷方法,代碼如下:
-(BOOL)isEqualToStudent:(Student *)otherStudent{ if(self == otherStudent) return YES; if(![_name isEqualToString:otherStudent.name]) return NO; if(_age != otherStudent.age) return NO; return YES; } -
在撰寫完判定方法后,應改寫類中的isEqual實體方法,如果傳入的物件為同一個類時,采用剛剛寫的自定義方法來判定,否則就交給父類判斷:
-(BOOL)isEqual:(id)object{ if([self class] == [object class]) return [self isEqualToStudent:(Student *)object]; else return [super isEqual:object]; }
-
Hash
Hash主要是用于NSMutableSet中的判斷添加的新物件是否已經存在了容器當中,如果不存在,則將新物件放入Set容器中,而判斷的依據就是根據實體物件的Hash屬性,
但是因為不同物件的Hash值會有沖突的可能性(相同的物件Hash值一定相同,這里的相同指的是記憶體地址相同),所以最后如果哈希值發生沖突的話,則會呼叫類中的isEqual方法進行等同性判斷,
既然都會呼叫isEqual方法,那么為什么不直接按順序遍歷set容器,依次呼叫isEqual方法?
答:首先set容器是非順序容器,它的記憶體空間結構不是按順序存盤的,如果按插入的順序遍歷,開支很大(當然set的插入是無順序的),其次除非是大量資料的存盤才會發生沖突的可能,在少量資料的情況下基本上不會出現依次遍歷沖突物件的isEqual方法的沖突情況,這樣看來以O(1)的時間復雜度就可以完成的任務,當然就選擇是它了,
在NSObject物件中,Hash值是根據物件所在的記憶體空間來進行計算的,所以在修改了isEqual方法后,如果沒有修改Hash的setter方法,系統在執行set容器操作該物件時,仍然會以記憶體地址為準來判斷兩個物件是否相同,所以為了防止出現這種情況,在修改等同性判斷方法的時候應順便修改Hash的setter方法
-
自定義Hash方法
當自定義完isEqual后,一般為了防止后面的開發用到有關set容器出現意外,所以一般都會對Hash的Setter方法進行修改,代碼如下:
-(NSUInteger)hash{ //這里的字串只要能體現出類物件的name和age即可(依需求而定) NSString* stringToHash=[NSString stringWithFormat:@"%@ %i",_name,_age]; return [stringToHash hash]; }但這里有個弊端,因為在這里新建一個字串的開銷很大(與回傳一個屬性值相比較),所以一般采取下面這種策略:
-(NSUInteger)hash{ NSUInteger nameHash=[_name hash]; NSUInteger ageHash=_age; return nameHash^ageHash; }這種方法既考慮了開銷也考慮了屬性的相關性和隨機性,---------Effective OC
-
Hash的呼叫順序
例子如下:
//Student.m -(BOOL)isEqualToStudent:(Student *)otherStudent{ if(self == otherStudent){ NSLog(@"(%@:%i)與(%@:%i)沖突了",_name,_age,otherStudent.name,otherStudent.age); return YES; } if(![_name isEqualToString:otherStudent.name]) return NO; if(_age != otherStudent.age) return NO; NSLog(@"[%@:%i]與[%@:%i]沖突了",_name,_age,otherStudent.name,otherStudent.age); return YES; } -(BOOL)isEqual:(id)object{ if([self class] == [object class]) return [self isEqualToStudent:(Student *)object]; else return [super isEqual:object]; } -(NSUInteger)hash{ return _age; } -(NSString *)description{ return [NSString stringWithFormat:@"%@:%i",_name,_age ]; }//main.m Student* stu1 = [[Student alloc] initWithName:@"小明" age:10]; Student* stu2 = [[Student alloc] initWithName:@"小明" age:10]; Student* stu3 = [[Student alloc] initWithName:@"小明" age:0]; NSMutableSet* set = [NSMutableSet set]; [set addObject:stu1]; //步驟1 [set addObject:stu2]; //步驟2 [set addObject:stu3]; //步驟3 NSLog(@"%@",set);輸出結果:
2020-05-08 22:50:12.172207+0800 effective-OC-test[83370:76838657] [小明:10]與[小明:10]沖突了 2020-05-08 22:50:12.172542+0800 effective-OC-test[83370:76838657] {( 小明:0, 小明:10 )} Program ended with exit code: 0-
當執行步驟1時,會先去呼叫hash方法得到物件的哈希值,因為set容器為空,所以直接根據hash值將物件存入記憶體當中,stu1成功存入容器中;
-
當執行步驟2時,同樣會先去呼叫hash方法得到哈希值,因為這里的hash值與age有關,stu1.age==stu2.age,所以兩個物件的哈希值相同,這時候就回去呼叫isEqual方法去判斷是否兩個物件是等同的,又因為我這里設定的當姓名和年齡一致時,即判斷為同一物件,所以這里會列印出沖突,并且不會將stu2存入容器中;
-
當執行步驟3時,呼叫完hash方法后,因為age不同,所以得到的hash值不同(很小幾率會出現相同),從而直接通過的到的hash值去分配記憶體空間,stu3成功存入容器中,
-
如果這里將isEqualToStudent的判斷條件修改一下,將年齡處修改為:
if(_age == otherStudent.age) return NO;這時候,再執行一遍,輸出如下:
2020-05-08 23:02:13.177534+0800 effective-OC-test[84327:76859194] {( 小明:0, 小明:10, 小明:10 )}這時候就會發現stu2已經成功存入了容器中,因為在hash值沖突后呼叫isEqualToStudent方法后,因為兩個年齡一致,回傳了NO,說明兩個物件這時候已經被當成了不同的兩個物件,
-
總結
- isEqual方法是用來判斷類物件的等同性,在自定義等同性判斷時,如果判斷物件與該物件同屬一個類時,就呼叫自己撰寫的判定方法,否則就交給父類的isEqual來解決,
- Hash值一般與容器相關,特別是Set容器,set容器通過物件的哈希值來分配記憶體地址,當遇到hash沖突后時會呼叫isEqual來進行二次驗證,
- 原生NSObject類的Hash值和isEqual方法都是根據物件的記憶體地址得到的結果,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/2560.html
標籤:iOS
上一篇:從一個iOS毛頭小子到現在的高級工程師, 我總結了一些經驗,先跟大家分享一下一些好的資料
下一篇:OC屬性與實體變數
