物件等同性判斷

scar1900發表於2019-09-20

物件等同性

概述

在實際的開發過程當中,經常需要比較物件是否相同。但是,==操作符比較出來的結果可能並不是想要的,因為這個操作符比較的是指標本身,並不是物件。所以大部分情況下,比價物件應該使用NSObject協議中的isEqual:

  • 一般來說,型別不同的物件總是不相等的。
  • 某些類會提供自己的等同性判定方法。

比如:

NSString *foo = @"Student 123";
NSString *bar = [NSString stringWithFormat:@"Student %i",123];
BOOL equalA = (foo == bar); //equalA = NO
BOOL equalB = [foo isEqaul:bar]; //equalA = NO
BOOL equalC = [foo isEqualToString:bar]; //equalC = YES
複製程式碼

這裡可以看出,NSString有著自己的等同性判斷方法isEqualToString:。呼叫這個方法會比呼叫isEqual:方法快,因為後者需要執行額外的步驟檢測受測物件的型別。

判斷過程

判斷物件等同性在NSObject協議中有兩個關鍵方法:

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
複製程式碼

這兩個方法的預設實現是兩個物件記憶體地址完全(指標值)相等時,這兩個物件才相等。在自定義物件中,正確重寫這兩個方法的步驟應該是:

重寫isEqual:返回YES --> Hash值相等 --> 呼叫isEqual:方法判斷物件等同
複製程式碼

舉例

@interface EOCPerson : NSObject
@property (nonatomic, copy)NSString *firstName;
@property (nonatomic, copy)NSString *lastName;
@property (nonatomic, assign)NSUInteger *age;
@end
複製程式碼

很明顯,當這個物件所有欄位都相同時,就可以認定兩個物件相等了。所以重寫isEqual:方法:

- (BOOL)isEqual:(id)object {
    ///判斷指標相等
    if (self == object) return YES;
    ///不同類不相等
    if ([self class] != [object class]) return NO;
    
    
    EOCPerson *otherPerson = (EOCPerson*)object;
    if (![_firstName isEqualToString:otherPerson.firstName]) {
        return NO;
    }
    if (![_lastName isEqualToString:otherPerson.lastName]) {
        return NO;
    }
    if (_age != otherPerson.age) {
        return NO;
    }
    return YES;
}
複製程式碼

首先判斷了指標是否相等,之後判斷所屬的類。不過在判斷類的時候,需要特別注意有繼承的情況,如果判斷子類是否與父類相等,就不能這麼粗暴的重寫isEqual方法了,需要根據情況,自定義一個子類和父類的比較規則。

之後就需要實現hash方法了,最先想到的是這樣:

- (NSUInterger)hash {
    return 1024;
}
複製程式碼

這樣實現之後確實能實現兩個EOCPerson物件的等同性判斷了。但在一些場景中會產生效能問題。比如一個用set實現的collection,collocation在檢索雜湊表時,會用雜湊值作為索引。每次向collection新增新物件時,優先會檢查雜湊值相同的相關物件,再判斷等同性。所以,如果已經有了10000個物件在set中,此時新增一個新物件,按照上個例子中的實現,就需要遍歷完10000個物件才能完成插入物件的操作。

那麼我們將上述例子做一下改進:

- (NSUInterger)hash {
    NSString *stringToHash = [NSString stringWithFormat:@"%@:%@:%i", _firstName, _LastName, _age];
    return [stringToHash hash];
}
複製程式碼

這麼做就能避免剛才所說的問題了,提高set操作時的檢索效率,但字串操作相對返回一個單一值來說仍然是一個耗時過程,會影響set的效能。

再進一步的優化一下:

- (NSUInterger)hash {
    NSUInterger firstNameHash = [_firstName hash];
    NSUInterger lastNameHash = [_lastName hash];
    NSUInterger ageHash = _age;
    return firstNameHash ^ lastNameHash ^ ageHash;
}
複製程式碼

這樣既解決了雜湊索引的問題,有提高了雜湊值生成的效率。所以雜湊值的生成有兩個注意點:

  • 減少碰撞頻率
  • 降低運算複雜度

等同性執行深度

在判斷陣列的是否等同的時候,需要判斷陣列內所有物件都相同,我們把這個叫做“深度等同性判斷”。但有時候,根據不同的場景,不需要判斷所有物件或者物件的所有屬性都一樣,這樣就可以降低等同性判斷的執行深度。

比如陣列的資料是從資料庫裡取出的,這個時候只要判斷資料物件對應的主鍵是否相等,便能判斷這個物件的等同性。

相關文章