從一次測試提出的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
的調用過程,結果以下:
isEqual
方法的調用就是不肯定的了,推測應該是根據系統哈希表的底層實現來的。 已有10個元素的時候:
已有22個元素的時候:
已有113個元素的時候:
從上圖結果來看,咱們能確定的得出在調用
containsObject
方法的時候,確定會調用傳入對象的
hash
方法,可是
isEqual
方法的調用就沒有什麼規律可循了,猜想的結果是:當集合中元素多的時候,調用
isEqual
方法的次數少,多是系統對哈希表的實現,當表中裝入的元素多的時候,元素在表中的存放位置越接近正態分佈。當表中元素少的時候,系統爲例節省空間,可能只會開闢一個桶或者不多的桶來存放元素。
除了NSSet集合類底層是用哈希表實現的,iOS的NSDictionary底層也是用哈希表實現的,那咱們來看下,當model存放在字典或者當作字典的key時,是否是也會調用hash方法。
<NSCopying>
協議,並實現- (id)copyWithZone:(nullable NSZone *)zone
方法】,會調用hash方法,並且最終存入字典的key是model的一份copy【避免以後的model更改,影響到存入時候的hash值,從而致使找不到對應的value】。此次我也多添加了好多個值到字典中,從運行結果來看,model做爲key的時候會調用hash方法,可是也看到了isEqual
的調用,並且調用的次數沒有什麼規律。這個我就真的有點迷惑了,若有哪位大佬看到,知道這個緣由的話,還請不吝賜教!!😁測試結果以下圖:
綜上:要想給自定義的類定義相等的話,須要重寫isEqual
和hash
方法,hash
值能夠用標識該類型實例的屬性異或
來得出。在model存入集合或做爲字典的key的時候,會調用model的hash
方法,而isEqual
的調用猜想應該是根據系統底層哈希表的實現來的。
本人能力有限,若有理解不對的地方,還請指出,謝謝!!!
參考致謝: