NSObject類的實例方法: - (BOOL)isEqual:(id)object
主要是根據對象的內存地址來判斷兩個對象是否相等,這裏與 ==
效果相同。code
isEqualToStringorm
(BOOL)isEqualToString:(NSString *)aString 是NSString類的實例方法,它主要用於比較兩個字符串中的內容是否相同,而非比較兩個字符串所在內存地址。該方法經常使用。對象
自定義類的isEqualip
在開發需求中,若是有比較兩個類對象是否具備等同性時,一般會根據需求來對父類的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主要是用於NSMutableSet中的判斷添加的新對象是否已經存在了容器當中,若是不存在,則將新對象放入Set容器中,而判斷的依據就是根據實例對象的Hash屬性。string
可是由於不一樣對象的Hash值會有衝突的可能性(相同的對象Hash值必定相同,這裏的相同指的是內存地址相同),因此最後若是哈希值發生衝突的話,則會調用類中的isEqual方法進行等同性判斷。hash
既然都會調用isEqual方法,那麼爲何不直接按順序遍歷set容器,依次調用isEqual方法?it
答:首先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,說明兩個對象這時候已經被當成了不一樣的兩個對象。