从一次测试提出的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
- (BOOL)isEqual:(id)object
方法。添加元素时,走hash方法是为了给元素在集合中算出一个合适的存放位置
【NSSet底层是用哈希表实现的】
。在集合中元素数量大于一的时候,再添加新元素的时候,会根据当前已有元素的存放位置【系统的哈希表实现应该会和元素的个数有关,哈希表中有个桶的概念,每一个桶又是一个链表或数组,深刻探究能够去看哈希表的相关知识】来决定是否走
isEqual:
方法。我测试了几回不一样的状况,有的时候不调用,有的时候调用,但调用的次数不必定会等于已有元素的个数。 测试结果以下: 添加两个自定义model后,又添加了一个字符串。运行结果:
除了addObject
方法(至关于存/写)会调用hash
方法外,那取/读的方法会不会调用?因为NSSet类没有相似NSDictionary
的经过key或数组的经过索引的方法去取某一个元素,咱们把目标放到containsObject
方法上来,这个方法判断集合中含有某个元素与否,按照个人想法,应该是经过传入元素的hash值,找到元素在哈希表中的位置,看这个位置上有没有值【若是有碰撞冲突的话,经过其余方法(遍历)找遍该位置上的值】看与传入的值是否相等。
经过判断有与没有两种状况查看containsObject
的调用过程,结果以下:
isEqual
方法的调用就是不肯定的了,推测应该是根据系统哈希表的底层实现来的。 已有10个元素的时候:
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
的调用猜想应该是根据系统底层哈希表的实现来的。
本人能力有限,若有理解不对的地方,还请指出,谢谢!!!
参考致谢: