iOS-对象相等性

Objective-C中对象的相等性是经常被忽略的一块,开发中经常用到isEqual,isEqualToString,==进行对象比较.Objective-C如果两个对象的内存地址一样,那么对象肯定相等,可以称之为对象本体性.
如果两个对象内存地址不一样,但是其他的数据都一样,那么对象也相等,可以称之为等同性.

- (BOOL)isEqual:(id)obj {
    return obj == self;
}

在 Foundation 框架中,一般NSObject 的子类都有自己的相等性检查实现,例如:

  • NSAttributedString -isEqualToAttributedString:
  • NSData -isEqualToData:
  • NSDate -isEqualToDate:
  • NSDictionary -isEqualToDictionary:
  • NSHashTable -isEqualToHashTable:
  • NSIndexSet -isEqualToIndexSet:
  • NSNumber -isEqualToNumber:
  • NSOrderedSet -isEqualToOrderedSet:
  • NSSet -isEqualToSet:
  • NSString -isEqualToString:
  • NSTimeZone -isEqualToTimeZone:
  • NSValue -isEqualToValue:

如果想比较对象是否相等,建议使用以上方法进行比较,而不是简单的通过isEqual进行对象比较.

字符串

不要通过==进行字符串判断,代码如下:

NSString *str1 = @"FlyElephant";
    NSString *str2 = @"FlyElephant";
    
    NSLog(@"%p---%p",str1,str2);
    if (str1 == str2) {
        NSLog(@"字符串相等");
    }

👆的代码我们期望是不相等,但是因为字符串驻留的优化技术,所有的字符串都是在一个共享字符串池中的,将不可变字符串的值赋值给了不同的指针.

isEqual

比较两个对象是否相同,需要根据具体的对象的数据进行系统比较,建立Person类,定义姓名,年龄,地区三个字段:

@interface Person : NSObject<NSCopying>

@property (copy, nonatomic) NSString *name;

@property (assign, nonatomic) NSUInteger age;

@property (copy, nonatomic) NSString *location;

- (BOOL)isEqualToPerson:(Person *)person;

@end
- (BOOL)isEqualToPerson:(Person *)person {
    
    if (!person) {
        return NO;
    }
    
    BOOL isEqualName = (!self.name && !person.name) || [self.name isEqualToString:person.name];
    BOOL isEqualAge = self.age == person.age;
    BOOL isEqualLocation = (!self.location && !person.location) || [self.location isEqualToString:person.location];
    
    return isEqualName & isEqualAge & isEqualLocation;
}

如果比较两个Person是否相等,通过isEqualPeron来比较:

   Person *person1 = [[Person alloc] init];
    person1.name = @"FlyElephant";
    person1.age = 27;
    person1.location = @"北京";
    
    Person *person2 = [[Person alloc] init];
    person2.name = @"FlyElephant";
    person2.age = 27;
    person2.location = @"北京";
    
    BOOL isEqulPerson = [person1 isEqualToPerson:person2];
    
    if (isEqulPerson) {
        NSLog(@"person1与person2相等");
    } else {
        NSLog(@"两者不相等");
    }

如果传入对象就是自身,那么就不需要进行之后的判断,同时为了避免出现较多的比较方法,重写isEqual方法,将isEqualToPerson方法内置在其中即可.

- (BOOL)isEqual:(id)object {
    if (self == object) {
        return YES;
    }
    
    if (![object isKindOfClass:[Person class]]) {
        return NO;
    }
    
    return [self isEqualToPerson:(Person *)object];
}

通过isEqual的判断结果与isEqualPerson结果一致.

   BOOL isEqual = [person1 isEqualToPerson:person2];
    if (isEqual) {
        NSLog(@"isEqual:person1与person2相等");
    } else {
        NSLog(@"isEqual:两者不相等");
    }

hash

对象比较的过程经常会听到如果两个对象相等,那么hash值一定相等,两个对象的hash值相等,对象不一定相等.

👆代码中person1与person2的比较过程并没有调用hash,那么什么时候会调用对象的hash方法呢?

- (NSUInteger)hash {
    NSUInteger hash = [super hash];
    NSLog(@"Person调用hash方法 = %ld", hash);
    return hash;
}

通过Objective-C集合类,NSMutableArray,NSMutableSet,NSMutableDictonary添加Person对象.

    Person *person1 = [[Person alloc] init];
    person1.name = @"FlyElephant";
    person1.age = 27;
    person1.location = @"北京";
    
    Person *person2 = [[Person alloc] init];
    person2.name = @"FlyElephant1";
    person2.age = 28;
    person2.location = @"北京";
    
    NSMutableArray *arr1 = [[NSMutableArray alloc] initWithObjects:person1, nil];
    NSMutableArray *arr2 = [[NSMutableArray alloc] initWithObjects:person2, nil];
    
    NSLog(@"数组比较结束----------------------");
    NSMutableSet *set1 = [NSMutableSet set];
    [set1 addObject:person1];
    NSMutableSet *set2 = [NSMutableSet set];
    [set2 addObject:person2];
    NSLog(@"NSMutableSet添加结束 -------------------------------");
    
    NSMutableDictionary *dictionaryValue1 = [NSMutableDictionary dictionary];
    [dictionaryValue1 setObject:person1 forKey:@"dic1"];
    NSMutableDictionary *dictionaryValue2 = [NSMutableDictionary dictionary];
    [dictionaryValue2 setObject:person2 forKey:@"dic2"];
    NSLog(@"dictionary 将person设置为值-------------------------------");
    
    NSMutableDictionary *dictionaryKey1 = [NSMutableDictionary dictionary];
    [dictionaryKey1 setObject:@"1" forKey:person1];
    NSMutableDictionary *dictionaryKey2 = [NSMutableDictionary dictionary];
    [dictionaryKey2 setObject:@"2" forKey:person2];
    NSLog(@"dictionary 将person设置为键 -------------------------------");
数组比较结束----------------------
Person调用hash方法 = 106102872298336
Person调用hash方法 = 106102872298336
Person调用hash方法 = 106102872298432
Person调用hash方法 = 106102872298432
NSMutableSet添加结束 -------------------------------
dictionary 将person设置为值-------------------------------
Person调用hash方法 = 106102872298336
Person调用hash方法 = 106102872298496
Person调用hash方法 = 106102872298432
Person调用hash方法 = 106102872298528
dictionary 将person设置为键 -------------------------------

Person设置为字典的key值时需要实现NSCopying协议:

- (id)copyWithZone:(NSZone *)zone {
    Person *model = [[[self class] allocWithZone:zone] init];
    model.name = self.name;
    model.age  = self.age;
    model.location = self.location;
    return model;
}

散列表(Hash Table)是程序设计中基础的数据结构之一,它使得 NSSet 和NSDictionary能够非常快速地(O(1)) 进行元素查找.

数组中的元素在内存中的分布是连续的地址,如果需要判断某个元素是否存在,需要进行逐步遍历.

散列表通过散列函数计算出散列值,实现元素的均匀分布,当不同的元素出现相同的散列值会出现散列碰撞.散列表同样可以通过元素的散列值很快查找到元素.

NSSet会根据元素的散列值判断元素重复,NSDictionary中key是不能重复的,所有会要求元素的散列值唯一,这两个集合会调用对象的hash方法.

不同元素的hash方法实现可以有不同的方式,如果是一个时间对象,hash实现如下:

- (NSUInteger)hash {
  return (NSUInteger)abs([self timeIntervalSinceReferenceDate]);
}

对于UIColor对象,可以通过位移实现:

- (NSUInteger)hash {
  CGFloat red, green, blue;
  [self getRed:&red green:&green blue:&blue alpha:nil];
  return ((NSUInteger)(red * 255) << 16) + ((NSUInteger)(green * 255) << 8) + (NSUInteger)(blue * 255);
}

大神Mattt Thompson建议:
大多数情况我们只需要对于关键属性的散列值进行一个简单的 异或(XOR)操作,就能够满足在 99% 的情况下的需求了.

- (NSUInteger)hash {
    return [self.name hash] ^ [self.location hash];
}

参考链接:
iOS-isEqual,isEqualToString和==区别
equality
iOS开发 之 不要告诉我你真的懂isEqual与hash!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,652评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,678评论 3 401
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,645评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,483评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,503评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,039评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,420评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,391评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,921评论 1 322
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,973评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,114评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,761评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,437评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,928评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,042评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,596评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,142评论 2 363

推荐阅读更多精彩内容

  • 闲话少说,先说本编博客的核心 iOS系统API给我们提供一个自动过滤重复元素的容器 NSMutableSet...
    upworld阅读 2,861评论 8 21
  • 前言 对数据的等同性判断包括对基本数据类型等同性的判断和对象等同性的判断。对基本数据类型等同性的判断是非常简单的,...
    VV木公子阅读 1,474评论 0 8
  • 前言 哈希(Hash)或者说散列表,它是一种基础数据结构。Hash 表是一种特殊的数据结构,它同数组、链表以及二叉...
    ZhengYaWei阅读 17,176评论 13 107
  • Objc 相等性判断 今天做任务时遇到一个问题,情况是这样的:我新建一个类,然后创建一个这个类的对象,然后将这个类...
    凌巅阅读 1,517评论 0 1
  • 今日沪深两市双双低开,早间A股市场表现低迷,沪指早盘持续低位震荡,创业板表现比较强劲,午后随着上海本地股持续...
    牛译婕阅读 124评论 0 0