iOS中的isEqual和hash

從一次測試提出的bug提及:有次測試給我提了個bug,說訂單列表的新加載出來的第一條和上一頁的最後一條的數據同樣,是同一個訂單。並且還能常常重現,我就挺疑惑,若是要是重複,應該整個一頁都是重複的啊,爲何只有第一條重複,仍是偶爾重現。我直接查看後端返回數據,發現是後端偶爾會在下一頁返回上一頁的最後一條數據。後端找了一下子緣由後,說須要我這邊來去重,後端具體是什麼緣由我就不是那麼清楚了,反正最後權衡說前端去重是效率最高,也是最有效的辦法。(我也很無奈🤷啊)。前端


說到去重,我腦子裏首先出現的是NSSet類型,由於這個集合類型是不能添加相同的數據的。這就引出了這篇文章的主題:怎麼去定義相同,對於值類型(int、double等數據類型)的數據能夠直接經過==來判斷是否相等,而對象類型的數據你要經過==來判斷的話,就是直接比較兩個對象的地址是否相等,也就是判斷兩個指針是不是指向的同一個對象【NSString類型比較特殊,暫不作討論】後端

系統提供的Foundation中,有些類有本身的判斷相等的方法。數組

NSAttributedString -isEqualToAttributedString:
NSData -isEqualToData:
NSDate -isEqualToDate:
NSDictionary -isEqualToDictionary:
NSHashTable -isEqualToHashTable:
NSIndexSet -isEqualToIndexSet:
NSNumber -isEqualToNumber:
NSOrderedSet -isEqualToOrderedSet:
NSSet -isEqualToSet:
NSString -isEqualToString://這個應該是最經常使用的了吧。
NSTimeZone -isEqualToTimeZone:
NSValue -isEqualToValue:

複製代碼

可是,對於本身定義的模型類,怎樣去定義兩個對象相等。 先上答案:bash

@interface EqualModel : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger identifier;
- (BOOL)isEqualToModel:(EqualModel *)model;
@end

@implementation EqualModel
- (BOOL)isEqualToModel:(EqualModel *)model {
    if (!model) {
        return NO;
    }
    BOOL haveEqualNames = (!self.name && !model.name) || [self.name isEqualToString:model.name];
    BOOL haveEqualIdentifers = self.identifier == model.identifier;
    return haveEqualNames && haveEqualIdentifers;
}
- (BOOL)isEqual:(id)object {
    //NSLog(@"走了isEqual");
    if (self == object) {
        return YES;
    }
    
    if (![object isKindOfClass:[EqualModel class]]) {
        return NO;
    }
    
    return [self isEqualToModel:(EqualModel *)object];
}
- (NSUInteger)hash
{
    //NSLog(@"走了hash");
    return self.name.hash ^ self.identifier;
}
複製代碼

以上是引用Mattt大神的文章Equality的實現。ide

看到實現你可能會有疑問:不是隻要實現- (BOOL)isEqual:(id)object方法就好了,爲何要還要實現- (NSUInteger)hash?提到hash值,就得知道哈希表/散列表了。測試

繼承於NSObject的對象都有hash方法,由於該方法是<NSObject>協議裏聲明的一個方法,hash方法的默認實現是返回該對象的地址。ui

既然不知道爲何要實現hash方法,就用先看下hash方法什麼時候調用。(咱們知道NSSet類裏不能添加相同的對象,那咱們就先從這個類入手)atom

EqualModel的定義見上邊。spa

- (void)testSet
{
    NSMutableSet *set = [NSMutableSet set];
    
    EqualModel *model0 = [[EqualModel alloc]init];
    model0.name = @"model0";
    model0.identifier = 0;
    NSLog(@"-------0--前");
    [set addObject:model0];
    NSLog(@"-------0--後");
    
    EqualModel *model1 = [[EqualModel alloc]init];
    model1.name = @"model1";
    model1.identifier = 1;
    NSLog(@"-------1--前");
    [set addObject:model1];
    NSLog(@"-------1--後");
    
}
複製代碼

結果以下:3d

由上邊結果能夠看出:(1)在爲空set添加對象時,只走了hash方法。(2)在set中有元素的時候,再添加新元素的時候,走了hash方法後,還走了 - (BOOL)isEqual:(id)object方法。添加元素時,走hash方法是爲了給元素在集合中算出一個合適的存放位置 【NSSet底層是用哈希表實現的】。在集合中元素數量大於一的時候,再添加新元素的時候,會根據當前已有元素的存放位置【系統的哈希表實現應該會和元素的個數有關,哈希表中有個桶的概念,每一個桶又是一個鏈表或數組,深刻探究能夠去看哈希表的相關知識】來決定是否走 isEqual:方法。我測試了幾回不一樣的狀況,有的時候不調用,有的時候調用,但調用的次數不必定會等於已有元素的個數。 測試結果以下: 添加兩個自定義model後,又添加了一個字符串。運行結果:
集合中已有10個元素的時候,添加一個字符串:
集合中已有10個元素的時候,添加一個model:
集合中已有100個元素的時候,添加一個model:

除了addObject方法(至關於存/寫)會調用hash方法外,那取/讀的方法會不會調用?因爲NSSet類沒有相似NSDictionary的經過key或數組的經過索引的方法去取某一個元素,咱們把目標放到containsObject方法上來,這個方法判斷集合中含有某個元素與否,按照個人想法,應該是經過傳入元素的hash值,找到元素在哈希表中的位置,看這個位置上有沒有值【若是有碰撞衝突的話,經過其餘方法(遍歷)找遍該位置上的值】看與傳入的值是否相等。

經過判斷有與沒有兩種狀況查看containsObject的調用過程,結果以下:

從結果中來看: 不論是包含不包含傳入的元素,都會調用hash方法,可是 isEqual方法的調用就是不肯定的了,推測應該是根據系統哈希表的底層實現來的。 已有10個元素的時候:
已有22個元素的時候:
已有113個元素的時候:
從上圖結果來看,咱們能確定的得出在調用 containsObject方法的時候,確定會調用傳入對象的 hash方法,可是 isEqual方法的調用就沒有什麼規律可循了,猜想的結果是:當集合中元素多的時候,調用 isEqual方法的次數少,多是系統對哈希表的實現,當表中裝入的元素多的時候,元素在表中的存放位置越接近正態分佈。當表中元素少的時候,系統爲例節省空間,可能只會開闢一個桶或者不多的桶來存放元素。


除了NSSet集合類底層是用哈希表實現的,iOS的NSDictionary底層也是用哈希表實現的,那咱們來看下,當model存放在字典或者當作字典的key時,是否是也會調用hash方法。

  • model做爲value存入字典結果:
  • model做爲字典的key的結果:
    能夠看到當model做爲key的時候【要想做爲key,須要遵循<NSCopying>協議,並實現- (id)copyWithZone:(nullable NSZone *)zone方法】,會調用hash方法,並且最終存入字典的key是model的一份copy【避免以後的model更改,影響到存入時候的hash值,從而致使找不到對應的value】。

此次我也多添加了好多個值到字典中,從運行結果來看,model做爲key的時候會調用hash方法,可是也看到了isEqual的調用,並且調用的次數沒有什麼規律。這個我就真的有點迷惑了,若有哪位大佬看到,知道這個緣由的話,還請不吝賜教!!😁測試結果以下圖:

綜上:要想給自定義的類定義相等的話,須要重寫isEqualhash方法,hash值能夠用標識該類型實例的屬性異或來得出。在model存入集合或做爲字典的key的時候,會調用model的hash方法,而isEqual的調用猜想應該是根據系統底層哈希表的實現來的。

本人能力有限,若有理解不對的地方,還請指出,謝謝!!!


參考致謝:

Equality中文

Equality英文

iOS開發 之 不要告訴我你真的懂isEqual與hash!

相關文章
相關標籤/搜索