isEqual
比較對象是否相等,在OC中目前有兩種方式:
== 和 isEqual:方法。
對于==方法,對于指針類對象的判斷,其實挺雞肋的,系統只是簡單的進行指針地址的對比。當我們想更“深入”地對比對象的時候,顯然==是不能滿足要求的。
對于我們在框架中常用的類型對象,比如NSString,NSArray類等,系統對于這些類,已經實現了一個isEqual方法可以幫助我們去對對象做比較。對于NSString類,還有一個特性的方法:
- (BOOL)isEqualToString:(NSString *)aString;
由此增加這個相等方法對于類型的判斷。提高對比效率,我們在自己實現isEqualTo方法是也要加強類型判斷。
下面兩個方法是比較對象時最為關鍵的兩個方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
默認的isEqual實現與==類似,都是直接比較指針指向地址是否相同。
我們在自己重寫isEqual時也應該重寫hash方法,由此保證,相同的兩個對象返回的hash是一樣的。但是有相同hash的對象,卻又不一定的相等的。
下面是一個重寫isEqual的例子:
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
if ([self class] != [object class]) {
return NO;
}
Teacher *anotherTeacher = (Teacher *)object;
if (![self.name isEqualToString:anotherTeacher.name]) {
return NO;
}
if (![self.workNumber isEqualToString:anotherTeacher.workNumber]) {
return NO;
}
return YES;
}
以上是一個Teacher類的對比方法,首先對比指針,如果self 和object相同,那么必定相等。然后我們在做類型判斷,如果兩者類型都不相等,那么必定不相等。但是如果有一個類叫JohnTeacher,固定name為john,是Teacher的子類,此時如果一個Teacher的name屬性為john,那么兩者是可能相等的,此時在isEqual方法實現中,就要對類型的判斷做更加細致的判斷,建議使用isKindOfClass進行判斷。最后就是進行對象的各屬性是否相等的判斷了,如果屬性都相等,即可認為兩個對象相等。
為保證相同對象都有相同的hash值這樣一個規定,重寫hash也是必須的。
比如可以這樣:
-(NSUInteger)hash{
return 1337;
}
這樣來說,相同的類的對象都有同一個Hash。
但是在一個集合使用的場景下,比如向一個NSSet(不允許對象重復)中添加對象。那個這個set在添加對象的時候,就會先去對比Hash值,發現hash相同,那么會再去調用isEqual去進一步判斷對象是否相等,如果添加的對象過多,那么每一次都會調用isEqual,造成執行效率低。
另一種hash的實現方法如下:
-(NSUInteger)hash{
NSString *stringToHash = [NSString stringWithFormat:@"%@:%@",_name,_workNumber];
return [stringToHash hash];
}
對由對象屬性拼接而成的字符串進行一次hash計算,最后返回這個值,由此可以確保相同對象,hash值一定是一樣的。但是這種方法肯定不如返回規定值來的快。
最后總結一個最合適的方法:
-(NSUInteger)hash{
NSUInteger nameHash = [_name hash];
NSUInteger workNumberHash = [_workNumber hash];
return nameHash ^ workNumberHash;
}
以上是一種相對折中的方法,hash值不會單一,執行量也不算太大。
規定類型的判斷方法
對于NSString來說,isEqualToString肯定要比isEqual方法來的更易讀,類型的規定也更加的明確。
所以我們也可以實現自己的強類型判斷的方法:
- (BOOL)isEqualToTeacher:(Teacher *)anotherTeacher
{
if (self == anotherTeacher) {
return YES;
}
if (![self.name isEqualToString:anotherTeacher.name]) {
return NO;
}
if (![self.workNumber isEqualToString:anotherTeacher.workNumber]) {
return NO;
}
return YES;
}
如此,在isEqual方法中,我們也可以寫的更簡單一點:
- (BOOL)isEqual:(id)object
{
if ([self class] == [object class]) {
return [self isEqualToTeacher:object];
}else{
return [super isEqual:object];
}
}
深相等與淺相等
以上isEqual中我們都對比了對象的每一個屬性是否相等,這樣的一種詳盡的對比方式可以稱為“深相等”
但是某些時候,比如Teacher,我們可以給他分配一個標識屬性,比如identifier。這個identifier對外只讀,并且每個對象identifier不相同,由此我們可以直接對比identifier即可,該方法為“淺相等”。
可變對象的對比
下面這個例子,中文有點難以描述,大致意思是可變對象的hash會隨對象的改變而改變,將對象加入一個集合中時可能會引發問題。直接上代碼:
NSMutableSet *set = [[NSMutableSet alloc] init];
NSMutableArray *arrayA = [@[@1,@2] mutableCopy];
[set addObject:arrayA];
NSLog(@"1: set = %@", set);
NSMutableArray *arrayB = [@[@1,@2] mutableCopy];
[set addObject:arrayB];
NSLog(@"2: set = %@", set);
NSMutableArray *arrayC = [@[@1] mutableCopy];
[set addObject:arrayC];
NSLog(@"3: set = %@",set);
[arrayC addObject:@2];
NSLog(@"4: set = %@",set);
NSSet *setB = [set copy];
NSLog(@"5: setB = %@",setB);
整理后的打印結果:
可以觀察到第四個輸出結果set = {(1,2),(1,2)},顯然與Set的定義相違背(不能有相同對象),而且更奇怪的是setB 拷貝了set后變成了{(1,2)}
所以我們在遇到這樣的情況時,不要修改被加入集合后的對象,否則導致像以上代碼那樣的混亂。
總結
- 1 如果想自定義對比對象的方法,重寫isEqual和hash方法。
- 2 相同對象,hash必須相同,但擁有相同hash的對象不一定本身相同。
- 3 有選擇的對比對象屬性,而不是全部對比(針對有特殊標識屬性的對象)
- 4 hash方法要可讀并高效