内容简介:从一次测试提出的bug说起:有次测试给我提了个bug,说订单列表的新加载出来的第一条和上一页的最后一条的数据一样,是同一个订单。而且还能经常重现,我就挺疑惑,如果要是重复,应该整个一页都是重复的啊,为什么只有第一条重复,还是偶尔重现。我直接查看后端返回数据,发现是后端偶尔会在下一页返回上一页的最后一条数据。后端找了一会儿原因后,说需要我这边来去重,后端具体是什么原因我就不是那么清楚了,反正最后权衡说前端去重是效率最高,也是最有效的办法。(我也很无奈 啊)。说到去重,我脑子里首先出现的是NSSet类型,因为这
从一次测试提出的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: 复制代码
但是,对于自己定义的模型类,怎样去定义两个对象相等。 先上答案:
@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的实现。
看到实现你可能会有疑问:不是只要实现 - (BOOL)isEqual:(id)object
方法就行了,为什么要还要实现 - (NSUInteger)hash
?提到 hash
值,就得知道哈希表/散列表了。
继承于 NSObject
的对象都有 hash
方法,因为该方法是 <NSObject>
协议里声明的一个方法, hash
方法的默认实现是返回该对象的地址。
既然不知道为什么要实现 hash
方法,就用先看下 hash
方法何时调用。(我们知道NSSet类里不能添加相同的对象,那我们就先从这个类入手)
EqualModel
的定义见上边。
- (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--后"); } 复制代码
结果如下:
由上边结果可以看出:(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方法。
- model作为value存入字典结果:
- model作为字典的key的结果:
可以看到当model作为key的时候【要想作为key,需要遵循
<NSCopying>
协议,并实现- (id)copyWithZone:(nullable NSZone *)zone
方法】,会调用hash方法,而且最终存入字典的key是model的一份copy【避免之后的model更改,影响到存入时候的hash值,从而导致找不到对应的value】。
这次我也多添加了好多个值到字典中,从运行结果来看,model作为key的时候会调用hash方法,但是也看到了 isEqual
的调用,而且调用的次数没有什么规律。这个我就真的有点迷惑了,如有哪位大佬看到,知道这个原因的话,还请不吝赐教!!:grin:测试结果如下图:
综上:要想给自定义的类定义相等的话,需要重写 isEqual
和 hash
方法, hash
值可以用标识该类型实例的属性 异或
来得出。在model存入集合或作为字典的key的时候,会调用model的 hash
方法,而 isEqual
的调用猜测应该是根据系统底层哈希表的实现来的。
本人能力有限,如有理解不对的地方,还请指出,谢谢!!!
参考致谢:
以上所述就是小编给大家介绍的《iOS中的isEqual和hash》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。