《Effective Objective-C 2.0 》 阅读笔记 item8

news/2025/1/17 7:42:44/

第8条:理解“对象等同性”这一概念

1. 对象等同性

“==”操作比较的是两个指针本身,而不是其所指的对象。
应该使用NSObject协议中声明的“isEqual:”方法来判断两个对象的等同性。其中,某些对象提供了特殊的“等同性判定方法”,如判断NSString类对象的“isEqualToString:”方法。

2. 判断等同性的关键方法

NSObject协议中有两个用于判断等同性的关键方法:

- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

NSObject类对这两个方法的默认实现是:当且仅当其“指针值”(pointer value)完全相等时,这两个对象才相等。
若想在自定义的对象中正确覆写这些方法,就必须先理解其约定。如果“isEqual:”方法判定两个对象相等,那么其hash方法也必须返回同一个值。但是,如果两个对象的hash方法返回同一个值,那么“isEqual:”方法未必会认为两者相等。

*** isEqual:方法的实现 ***

/* 某个类  */
@interface EOCPerson : NSObject
@property (nooatomic, copy) NSString *firstName;
@property (nooatomic, copy) NSString *lastName;
@property (nooatomic, assign) NSUInteger age;
@end/* isEqual:方法的实现  */
- (BOOL)isEqual:(id)object{// 判断两个对象的指针if (self == object) return YES;// 判断两个对象所属的类if ([self class] != [object class]) return NO;// 检查两个对象的属性是否都相等EOCPerson *otherPerson = (EOCPerson*)object;if (![_firstName isEqualToString:otherPerson.firstName])return NO;if (![_lastName isEqualToString:otherPerson.lastName])return NO;if (_age != otherPerson.age)return NO;return YES;
}

*** hash方法的实现 ***
根据等同性约定:若两对象相等,则其hash值也相等,但是两个hash值相同的对象却未必相等。 

/* 第一种实现方式  */
- (NSUInteger)hash{return 1337;
}
/*评价:在collection(集合类)中使用这种对象将产生性能问题,因为collection在检索哈希表(hash table)时,会用对象的哈希码做索引。*//* 第二种实现方式  */
- (NSUInteger)hash{NSString *stringToHash = [NSString stringWithFormat:@"%@:%@:%i", _firstName, _lastName, age];return [stringToHash hash];
}
/*评价:将NSString对象中的属性都塞入另外一个字符串中,然后令hash方法返回该字符串的hash值。这样做,符合“两个相等的对象返回相同的hash值”的约定,但是还需要负担创建字符串的开销,所以比返回单一值要慢。而且,把这种对象添加到collection中时,也会产生性能问题,因为要想添加,必须先计算其hash值。*//* 第三种实现方式  */
- (NSUInteger)hash{NSUInteger firstNameHash = [_firstName hash];NSUInteger lastNameHash = [_lastName hash];NSUInteger ageHash = _age;return firstNameHash ^ lastNameHash ^ ageHash;
}
/*评价:这种做法,既能保持较高效率,又能使生成的hash值至少位于一定范围之内,而不会过于频繁地重复。当然,此算法生成的hash值还是会碰撞(collision),不过至少可以保证hash值有多种可能的取值。*/

 

总结:编写hash方法时,应该用当前的对象做做实验,以便在减少碰撞频度与降低运算复杂程度之间取舍。

3. 特定类所具有的等同性判定方法

除了NSString之外,NSArray与NSDictionary类也具有特殊的等同性判定方法(分别为“isEqualToArray:”和“isEqualToDictionary:”方法)。
在编写特定类的判定方法时,也应一并覆写“isEqual:”方法。

/* EOCPerson类  */
// 在自己编写的判定方法中不用检测参数类型
- (BOOL)isEqualToPerson:(EOCPerson*)otherPerson{// 判断两个对象的指针if (self == object) return YES;// 检查两个对象的属性是否都相等EOCPerson *otherPerson = (EOCPerson*)object;if (![_firstName isEqualToString:otherPerson.firstName])return NO;if (![_lastName isEqualToString:otherPerson.lastName])return NO;if (_age != otherPerson.age)return NO;return YES;
}// 覆写isEqual:方法
// 如果是两对象所属的类就调用自己编写的判定方法,否则交由超类来判断。
- (BOOL)isEqual:(id)object{if ([self class] == [object class]){return [self isEqualToPerson:(EOCPerson*)object];}else{return [super isEqual:object];}
}

 

4. 等同性判定的执行深度

创建等同性判定方法时,需要决定是根据整个对象来判断等同性,还是仅仅根据其中几个字段来判断。前者叫做“深度等同性判定”,后者由于有名为“唯一标识符”的属性,可以根据标识符来判断等同性,尤其是该属性声明为readonly的时候。
是否需要在等同性判定方法中检测全部字段取决于受测对象。只有类的编写者才可以判定两个对象实例在何种情况下应判定为相等。

5. 容器中可变类的等同性

在collection中放入可变类对象的时候,需要确保hash值不是根据对象的“可变部分”计算出来的,或是保证放入collection之后就不再改变可变类对象的内容了。

要点

  • 若想检测对象的等同性,请提供“isEqual:”和“hash”方法。
  • 相同的对象必须具有相同的hash值,但是两个hash值相同的对象却未必相同
  • 不要盲目地逐个检测每条属性,而是应该依照具体需求来制定检测方案。
  • 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。


http://www.ppmy.cn/news/37816.html

相关文章

网易二面:MongoDB索引底层使用的是什么数据结构?

文章目录 mongoDB存储引擎对B-tree 的误解开始B 树的单条记录查询性能真的好于 B+ 树吗?B+ 树的优势为mongoDB存储引擎 mongoDB使用的存储引擎有: 1、WiredTiger存储引擎是mongodb3.2的默认存储引擎; 2、MMAPv1是mongodb基于内存映射最初的存储引擎; 3、In-Memory是一种…

【分布式】java实现分布式事务的五种方案

文章目录背景什么是分布式事务什么是分布式系统:什么是事务:什么是本地事务:什么是分布式事务:分布式事务有哪些应用场景:如何进行分布式事务控制CAP理论分布式系统如何兼顾CAP?CAP有哪些组合方式&#xff…

todo-list遇到的问题

vue事件修饰符 .stop event.stopPropagation() .prevent event.preventDefault()mysql默认字符集是 latin,,在插入中文的时候会报错, # docker中编辑mysql配置文件# docker进入容器docker exec -it mymysql /bin/bash # 安装vim yum update yum …

C++ - 继承 | 菱形继承

之前的文章中我们简要的讲述了C中继承部分的知识,但是还没有完全的讲完,在本文中将会讲到菱形继承的问题。 复杂的菱形继承 单继承:一个子类只有一个直接父类时称这个继承关系为单继承。 多继承:一个子类有两个或以上直接父类时…

函数设计—参数规则

【规则1-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。 如果函数没有参数,则用 void 填充。 例如: void SetValue(int width, int height); // 良好的风格 void SetValue(int, int); // 不良的风格 float GetValue(…

Android 9.0系统源码_窗口管理(三)WindowManagerService对窗口的管理过程

前言 上一篇我们具体分析了WindowManager的addView方法添加窗口的过程,我们知道WindowManager最终会调用到WindowManagerService的addWindow方法。本篇文章我们将在此基础上,具体来分析WindowManagerService的addWindow方法添加窗口的过程。这个方法的代…

selenium和Firefox的安装配置

selenium和firefox的安装配置1.1、Firefox的安装1.2、Firefox驱动geckodriver的安装1.3、geckodriver环境配置两种方式1.3.1、直接添加1.3.2、手动配置1.4、python安装selenium库两种方式1.4.1、使用pip命令进行安装1.4.2、Pycharm当中安装1.1、Firefox的安装 这之前我们先安装…

最新前端面试知识点总结-2023(3w+字,长篇幅)

2023-前端面试知识点总结面试题总览javascript相关一、js 代码的常用优化手段二、es5 构造函数与继承三、new 一个对象的过程四、防抖与节流五、promise/A规范概述六、实现一个柯里函数封装七、事件队列八、微任务是哪些宏任务是哪些九、执行js代码时,同步任务、微任…