相等的概念是探究哲学和数学的核心,并且对道德、公正和公共政策的问题有着深远的影响。

从一个经验主义者的角度来看,两个物体不能依据一些观测标准中分辨出来,它们就是相等的。在人文方面,平等主义者认为相等意味着要保持每个人的社会、经济、政治和他们住地的司法系统都一致。

对程序员来说,协调好逻辑和感官能力来理解我们塑造的'相同'的语义是一项任务。'相同的问题'(的探讨)太微妙,同时有太容易被忽视。对语义没有充分的理解就直接去实现它,可能会导致没必要的工作和不正确的结果。因此对数学和逻辑系统的深刻理解与按既定计划实现同样必要。

虽然所有的技术博文都是有诱惑你来读它的标题和代码,但请花几分钟时间来阅读和理解这些文字。逐字地复制看似有用的代码而不知道为什么这样写很有可能导致一些错误。相等性是个重要话题之一,但它仍包含了许多混乱的概念,尤其是在Objective-C中。

Equality & Identity

首先,弄清楚equality和identity的区别很重要。

如果两个物体具有相同的观测属性,它们是可以相互等同的。但是,这两个对象仍然可以分辨出差异,它们各自的identity。在程序中,一个对象的identity是和它的内存地址关联的。

NSObject对象测试和另一个对象是否相同使用isEqual:方法,在它的基本实现里性等性检查本质上是对identity的检查,如果两个对象指向了相同的内存地址,它们被认为是相等的。

@implementation NSObject (Approximate)
- (BOOL)isEqual:(id)object {
return self == object;
}
@end

对于内置的类,像NSArray, NSDictionary, 和 NSString,进行了一个深层的相等性比较,来测试在集合中的每个元素是否相等,这是一个应该也确实非常有用的做法。

NSObject 的子类要实现它们各自的isEqual:方法时,应该做到以下几点:

1.实现一个isEqualTo__ClassName__:方法来执行有意义的值比较.

2.重写isEqual: 方法 来作类型和对象identity检查, 回调上述的值比较方法.

3.重写 hash, 这个会在下一部分解释.

这里有一个NSArray实现这个的大概的思路(这个例子忽略了类簇, 实际实现会更具体复杂):

@implementation NSArray (Approximate)
- (BOOL)isEqualToArray:(NSArray *)array {
if (!array || [self count] != [array count]) {
return NO;
} for (NSUInteger idx = 0; idx < [array count]; idx++) {
if (![self[idx] isEqual:array[idx]]) {
return NO;
}
} return YES;
} - (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
} if (![object isKindOfClass:[NSArray class]]) {
return NO;
} return [self isEqualToArray:(NSArray *)object];
}
@end

下面的在Foudation中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:

当比较任何这些类的两个实例时,推荐使用它们各自的高级别的method而不是isEqual:

然而,我们的理论实现还没有完成,现在,让我们把注意力转向hash(一段插曲:先清理一下NSString的问题)。

NSString判等的奇怪案例

一个有趣的插曲,看一下这个代码:

NSString *a = @"Hello";
NSString *b = @"Hello";
BOOL wtf = (a == b); // YES

郑重地声明一下正确的比较两个NSString对象相等的方法是使用-isEqualToString:方法,无论如何也不能通过==操作符来比较两个NSString。

那么这里是怎么回事呢?为什么 NSArray或者NSDictionary字面量相同不会这样,而它(NSString)会这样呢。

这都是一种被称为字符串驻留的优化技术做的,因为这种优化不同的值可以对一份不可变的字符串值的备份进行拷贝。NSString类型的a指针和b指针对驻留字符串 @"Hello"进行了相同的拷贝。注意这个优化仅仅对静态声明的不可变字符串有效。

更有趣的是,OC的selector的名字也会被当做驻留字符串存储在一个共用的字符串pool中。

Hashing

最日常的面向对象编程来说,对象判等最主要的用法在于决定集合成员。为了让这一步更快一些,自定义判等实现的类应该也实现hash:

1.对象相等是相互的([a isEqual:b] ⇒ [b isEqual:a])

2.如果对象相等,它们的hash值必须相等([a isEqual:b] ⇒ [a hash] == [b hash])

但是,反过来不一定成立:如果它们的hash值相等,两个对象不一定相等。([a hash] == [b hash] ¬⇒ [a isEqual:b])

现在快速翻看一下《计算机科学》101:

hash表式编程中的基本的数据结构,它可以使NSSet & NSDictionary 快速地(O(1))查找它的元素。

我们也可以通过对比着数组很好地理解hash表:

Arrays按照有序的索引存储元素,因此一个大小为n的数组会把元素放在索引1,2直到n-1.为了确定数组中的一个元素存在了哪里,不得不一个个检查每个位置(除非数组碰巧已经排序好,但这是另一回事)。

Hash表使用了略微不同的方法。而不是按顺序存储元素,hash表在内存中分配了n个位置,同时用一个函数来计算在这个范围内计算一个位置。一个hash函数是确定性的,同时一个好的hash函数使用一个相对均匀的散列来生成值,而且不会有太多的计算过程。当两个不同的对象计算出相同的hash值时,会产生hash冲突。当冲突发生时,hash表会寻找冲突点同时把新加的对象放到第一个可用的位置。当hash表变得越来越拥挤,冲突的可能性会增加,这会导致花费更多的时间来寻找空间(这就是为什么均匀散列的hash函数不菲的原因。)

一个关于实现hash函数的错误共识来自于随之发生的断言,这个错误的共识认为hash值必须是不同的。这个错误共识会导致不必要地复杂实现,包括从Java textbooks复制过来的质数的神奇咒语。实际上,一个简单的对关键属性hash值的XOR(异或运算)对于99%的情况来说已经够用了。

技巧就是思考对象中的哪个值是关键的。

对NSDate来说,对参照日期的时间间隔已经够用了:

@implementation NSDate (Approximate)
- (NSUInteger)hash {
return (NSUInteger)abs([self timeIntervalSinceReferenceDate]);
}

对UIColor来说,移位之后的RGB值是非常方便计算的

@implementation UIColor (Approximate)
- (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);
}
@end

在子类中实现 -isEqual: 和 hash

综合在一起,这里有一个如何在子类重写默认的判等实现的例子:

@interface Person
@property NSString *name;
@property NSDate *birthday; - (BOOL)isEqualToPerson:(Person *)person;
@end @implementation Person - (BOOL)isEqualToPerson:(Person *)person {
if (!person) {
return NO;
} BOOL haveEqualNames = (!self.name && !person.name) || [self.name isEqualToString:person.name];
BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday]; return haveEqualNames && haveEqualBirthdays;
} #pragma mark - NSObject - (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
} if (![object isKindOfClass:[Person class]]) {
return NO;
} return [self isEqualToPerson:(Person *)object];
} - (NSUInteger)hash {
return [self.name hash] ^ [self.birthday hash];
} @end

如果想满足好奇心或者出于学究式的研究,看一下这个 Mike Ash的文章 ,解释了通过移位和翻转组合值如何改善了可能产生重叠(冲突)的hash.

本文完全翻译自: http://nshipster.com/equality/

iOS判断对象相等 重写isEqual、isEqualToClass、hash的更多相关文章

  1. 如何理解iOS的“对象等同性”

    在iOS开发过程中,我们经常需要用到等同性来判断两个对象是否相等,通常我们会使用==来判断,但是这样比较出来的结果可能不是我们期望的:所以,一般我们会使用NSObject协议声明的isEqual方法来 ...

  2. iOS开发 之 不要告诉我你真的懂isEqual与hash!

    目录 为什么要有isEqual方法? 如何重写自己的isEqual方法? 为什么要有hash方法? hash方法什么时候被调用? hash方法与判等的关系? 如何重写自己的hash方法? 为什么要有i ...

  3. Set集合判断对象重复的方法

    Set<User> userSet = new HashSet<>(); User user1= new User("aa","11") ...

  4. Javascript 判断对象是否相等

    在Javascript中相等运算包括"==","==="全等,两者不同之处,不必多数,本篇文章我们将来讲述如何判断两个对象是否相等? 你可能会认为,如果两个对象 ...

  5. scala 判断对象相等/equals

    package scala_enhance.scalaextends import scala.collection.mutable.HashMap /** * scala中判断对象相等 * 原则: ...

  6. 2.1.JVM的垃圾回收机制,判断对象是否死亡

    因为热爱,所以坚持. 文章下方有本文参考电子书和视频的下载地址哦~ 这节我们主要讲垃圾收集的一些基本概念,先了解垃圾收集是什么.然后触发条件是什么.最后虚拟机如何判断对象是否死亡. 一.前言   我们 ...

  7. c++派生类中构造函数和析构函数执行顺序、判断对象类型、抽象类、虚函数

    一. 代码: 1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #include&l ...

  8. 深入Java虚拟机--判断对象存活状态

    程序计数器,虚拟机栈和本地方法栈 首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较特别的.这个三个部分的特点 ...

  9. js中判断对象具体类型

    大家可能知道js中判断对象类型可以用typeof来判断.看下面的情况 <script> alert(typeof 1);//number alert(typeof "2" ...

随机推荐

  1. xpath 总结

    例如 <table id="MatchTable"> <tr id="Explain_1228761" style="display ...

  2. Team Foundation Server源代码管理多人开发的使用心得

    问题1:多人使用TFS源代码管理器的时候,往往会造成同个文件内源代码不一致,覆盖别人的代码. 解决方案: 给多个人分配不同的开发任务,保证每个人修改的文件都不会重叠. 但有些情况无法避免多个人同时修改 ...

  3. go:结构体的可访问性

    1.要使某个符号对其他包( package)可见(即可以访问),需要将该符号定义为以大写字母开头------摘自go相关书籍2.go只限制包内外的可访问性,而不限制同包内不同文件的可访问性 本文讨论结 ...

  4. 搭建Nginx+Java环境测试并且运行

    一.简介: Tomcat在高并发环境下处理动态请求时性能很低,而在处理静态页面更加脆弱.虽然Tomcat的最新版本支持epoll,但是通过Nginx来处理静态页面要比通过Tomcat处理在性能方面好很 ...

  5. 9. 了解 Cocoa-百度百科

    Cocoa是苹果公司为Mac OS X所创建的原生面向对象的API,是Mac OS X上五大API之一(其它四个是Carbon.POSIX.X11和Java). 苹果的面向对象开发框架,用来生成 Ma ...

  6. OpenLayers.2.10.Beginners.Guide---第一章

    从网上下载openlayers2,解压取得img theme 文件夹和openlayes.js文件.放在同一文件夹下用phpstorm打开. 创建index.html-------------每一行都 ...

  7. Top 命令详解

    Top 命令详解 先感受一下top命令的执行结果吧!哈哈-- top - 17:32:34 up 3 days, 8:04, 5 users, load average: 0.09, 0.12, 0. ...

  8. Struts2中Date日期转换的问题

      今天跑程序的时候莫名其妙的出现了下面的一个异常: java.lang.NoSuchMethodException:com.ca.agent.model.mybatis.ApprovalInforC ...

  9. PHP之session与cookie

    1.session与cookie的关系 众所周知,session是存储在服务器端,cookie是存储在客户端,如果禁用了浏览器的cookie功能,很多时候(除非进行了特殊配置)服务器端就无法再读取se ...

  10. gulp使用过程中出现的问题

    在使用gulp的过程中,最容易出现错误的地方就是在安装本地的gulp的时候,错误的原因有: 1.本来是局部安装gulp,但使用命令时还带-g. 2.忘记在局部安装gulp. 以上两种情况出错时会报错, ...