写点什么

iOS 中对象等同性 isEqual:和 hash

作者:NewBoy
  • 2022 年 7 月 24 日
  • 本文字数:2913 字

    阅读完需:约 10 分钟

对象等同性

  • 对象的等同性 :我们在使用对象的时候,往往需要判断两个对象是否相等,这种相等包含两种含义:

  • 在程序上是否是同一块内存地址

  • 在语义上,是否能指代同一对象


 NSString *str = @"123"; NSString *str1 = str; NSString *str2 = [NSString stringWithFormat:@"%d",123]; NSLog(@"%p -- %p -- %p",str,str1,str2);  NSLog(@"str == str1 : %d",str == str1); NSLog(@"str == str2 : %d",str == str2); NSLog(@"str isEqual str1 : %d",[str isEqual:str1]); NSLog(@"str isEqual str2 : %d",[str isEqual:str2]);
log:0x10d5060a8 -- 0x10d5060a8 -- 0xa000000003332313str == str1 : 1str == str2 : 0str isEqual str1 : 1str isEqual str2 : 1
复制代码


由此可见


  • ==:比较的是两个指针本身,而不是其所指的对象

  • isEqual:判断两个对象是否相等



判断等同性的方法

-(BOOL)isEqual:(id)object;
复制代码


@property (readonly) NSUInteger hash;
复制代码


  1. -(BOOL)isEqual:(id)object;这个方法是用来判断两个对象是否相等的。

  2. hash 是用在哈希表表结构的表地址。


由官方文档可知,


  • isEqual:方法返回YES,那么hash必然相等

  • 如果hash相等,那么isEqual:返回的不一定是YES


首先我们定义一个 Person 类,其中包含 name 和 age 俩个属性,当 person 的 name 和 age 同时一致时,我们认为这俩个对象是同一个人

-(BOOL)isEqual:(id)object;

- (BOOL)isEqual:(id)object{    //判断俩个对象的指针是否相同    if (self == object) {        return YES;      //判断俩个对象所属类是否相同    }else if (![object isKindOfClass:[self class]]) {        return NO;    }    Person *per = (Person *)object;    //判断每个属性是否相同    if ([_name isEqualToString:per.name] && _age == per.age) {        return YES;    }    return NO;}
复制代码


1、判断俩个对象的指针是否相同

2、判断俩个对象所属类是否相同

3、判断每个属性是否相同

- (NSUInteger)hash

  • 为什么要 hash 方法?

  • 比如我们要从数组中查找某个成员

  • step1:遍历数组中成员

  • step2:将取出的值与目标值比较,如果相等,则返回该成员

  • 这样查找比较复杂,为了提高查找速度,Hash Table 出现了,当成员被加入到 Hash Table 中时,会给它分配一个hash值,以标识该成员在集合中的位置,分配这个 hash 值(即用于查找集合、数组中成员的位置标识)就是通过 hash 算法计算出来的,且 hash 方法返回的 hash 值最好唯一,和数组相比,基于hash值索引的 hash Table 查找某个成员的过程就是

  • step1:通过 hash 值直接找到查找目标的位置

  • step2:如果目标位置上有多个相同hash值成员,此时再按照数组方式查找

  • hash 方法什么时候被调用?


     #import "Person.h"     @implementation Person     -(NSUInteger)hash {        NSLog(@"%s",__func__);        return 9999;     }     @end
Person *person1 = [[Person alloc] init]; person1.name = @"abc"; person1.age = 12;
NSMutableArray *array = [NSMutableArray arrayWithCapacity:10]; [array addObject:person1]; NSLog(@"array end"); NSLog(@"----------"); NSMutableSet *set = [NSMutableSet setWithCapacity:10]; [set addObject:person1]; NSLog(@"set end"); NSLog(@"----------"); NSMutableDictionary *dic = [NSMutableDictionary dictionary]; [dic setObject:person1 forKey:@"user"]; NSLog(@"dic end"); NSLog(@"----------");
复制代码


log:


    array end    -[Person hash]    set end    dic end
复制代码


NSSet添加新成员时, 需要根据`hash`值来快速查找成员, 以保证集合中是否已经存在该成员
复制代码


  • hash值的目的是尽最大可能返回一个标识,但并不是一定要是唯一的。根据 Person 的设定,能标识 Person 的是 name 和 age 属性,所以我们可以根据这两个属性来进行hash值的生成。


    - (NSUInteger)hash {        return [_name hash] ^ _age;    }
复制代码


  • 保证hash的不可变

  • 在容器中使用对象时,要尽量避免对象hash值的不可变,或者说在将对象放入容器后不再改变对象的内容,这样才能避免容器中出现错误的数据


    Person *person1 = [[Person alloc] init];    person1.name = @"abc";    person1.age = 12;
Person *person2 = [[Person alloc] init]; person2.name = @"def"; person2.age = 12;
NSMutableSet *set = [NSMutableSet setWithCapacity:10]; [set addObject:person1]; [set addObject:person2];
NSLog(@"before set: %@",set); NSLog(@"before person1 hash: %ld",[person1 hash]); NSLog(@"before person2 hash: %ld",[person2 hash]) person2.name = @"abc"; NSLog(@"after set: %@",set); NSLog(@"after person1 hash: %ld",[person1 hash]); NSLog(@"after person2 hash: %ld",[person2 hash]);
NSSet *lastSet = [set copy]; NSLog(@"lastSet:%@",lastSet);
log: before set: {( <Person: 0x60000045e420>, <Person: 0x60000045e840> )} before person1 hash: 516202365 before person2 hash: 517992654 after set: {( <Person: 0x60000045e420>, <Person: 0x60000045e840> )}
after person1 hash: 516202365 after person2 hash: 516202365 lastSet:{( <Person: 0x60000044ced0> )}
复制代码



自定义类的等同性判断

在编写判定方法时,需要重写“isEqual”方法,如果对比参数与实际类属于同一类(类似 isEqualToString:),那么就调用自己的判定方法,否则就交给父类判断例如 Person 类


#import <Foundation/Foundation.h>@interface Person : NSObject@property (nonatomic,copy) NSString *name;@property (nonatomic,assign) NSInteger age;
- (BOOL)isEqualToPerson:(Person *)person;@end
#import "Person.h"@implementation Person
- (BOOL)isEqualToPerson:(Person *)person{ if (self == person) { return YES; } if ([_name isEqualToString:person.name] && _age == person.age) { return YES; } return NO;}
- (BOOL)isEqual:(id)object{
if ([[object class] isKindOfClass:[self class]]) { return [self isEqualToPerson:object]; }else{ return [super isEqual:object]; }}
- (NSUInteger)hash { return [_name hash] ^ _age;}@end
复制代码



等同性判断的执行深度

  • 创建等同性判断方法时, 需要决定是根据整个对象来判断等同性还是仅根据几个字段来判断

  • NSArray检测方式是先看俩个数组所含对象个数是否相同,如果相同,那么每个位置的俩个对象身上调用isEqual方法,如果对应位置相等,那么俩个数组就相等,这种方式叫深度等同性判断

  • 如果我们的 Person 类的实例是根据数据库的数据创建的,那么其中会有一个属性被定义为主键,这种情况下我只需要根据主键标识来判断即可

  • 是否需要在等同性判断方法中检测全部字段取决于受测对象,只有类的编写者才可以确定俩个对象实例在何种情况下应判定为相等

发布于: 2 小时前阅读数: 7
用户头像

NewBoy

关注

虽不年少,艳阳高照 2019.04.10 加入

还未添加个人简介

评论

发布
暂无评论
iOS中对象等同性isEqual:和hash_ios_NewBoy_InfoQ写作社区