iOS开发 之 不要告诉我你真的懂isEqual与hash!
目录
为什么要有isEqual方法?
isEqual方法的作用大家肯定是知道的:
判断两个对象是否相等
但是判断相等不是已经有==运算符了么, 为什么还要isEqual方法?
这是因为:
对于基本类型, ==运算符比较的是值; 对于对象类型, ==运算符比较的是对象的地址(即是否为同一对象)
注意: 上述==运算符的说明适用于Objective-C和Java等不支持运算符重载的语言, 支持运算符重载的语言有C++
所以要理清==运算符和isEqual方法的区别, 问题就集中在
什么叫比较对象的地址, 什么叫比较对象
我们通过下面的例子来说明这个问题
UIColor *color1 = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
UIColor *color2 = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
NSLog(@"color1 == color2 = %@", color1 == color2 ? @"YES" : @"NO");
NSLog(@"[color1 isEqual:color2] = %@", [color1 isEqual:color2] ? @"YES" : @"NO");
打印结果如下
color1 == color2 = NO
[color1 isEqual:color2] = YES
从上面的例子可以看出, ==运算符只是简单地判断是否是同一个对象, 而isEqual方法可以判断对象是否相同, 例如UIColor对象表示的color是否相同
如何重写自己的isEqual方法?
对于Cocoa Framework中定义的类型, 例如上面例子中的UIColor, isEqual方法已经实现好了
常见类型的isEqual方法还有NSString isEqualToString / NSDate isEqualToDate / NSArray isEqualToArray / NSDictionary isEqualToDictionary / NSSet isEqualToSet, 更多参考Equality
但对于自定义类型来说, 通常需要重写isEqual方法
通过下面的例子, 我们来看看重写isEqual方法的正确姿势
<!--more-->
首先定义Person类如下
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;
@end
Person类中实现的isEqual方法如下
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[Person class]]) {
return NO;
}
return [self isEqualToPerson:(Person *)object];
}
- (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;
}
上述代码主要步骤如下
Step 1: ==运算符判断是否是同一对象, 因为同一对象必然完全相同
Step 2: 判断是否是同一类型, 这样不仅可以提高判等的效率, 还可以避免隐式类型转换带来的潜在风险
Step 3: 通过封装的isEqualToPerson方法, 提高代码复用性
Step 4: 判断person是否是nil, 做参数有效性检查
Step 5: 对各个属性分别使用默认判等方法进行判断
Step 6: 返回所有属性判等的与结果
isEqual的实现并不复杂, 但是从代码质量(效率, 安全, 复用)来说, 上述实现仍然值得仔细学习和借鉴
除了上面的最佳实践, 还有一种最不佳实践
@implementation NSDate (Approximate)
- (BOOL)isEqual:(id)object {
return YES;
}
@end
这里的isEqual方法一直返回YES
NSLog(@"[self.date1 isEqual:@\"hello\"] = %@", [self.date1 isEqual:@"hello"] ? @"YES" : @"NO");
打印结果如下
[self.date1 isEqual:@"hello"] = YES
这个有趣的实验说明: 对象的判等可以完全由您决定, 即使两个完全不同的对象
为什么要有hash方法?
这个问题要从Hash Table这种数据结构说起
首先我们看下如何在数组中查找某个成员
Step 1: 遍历数组中的成员
Step 2: 将取出的值与目标值比较, 如果相等, 则返回该成员
在数组未排序的情况下, 查找的时间复杂度是O(array_length)
为了提高查找的速度, Hash Table出现了
当成员被加入到Hash Table中时, 会给它分配一个hash值, 以标识该成员在集合中的位置
通过这个位置标识可以将查找的时间复杂度优化到O(1), 当然如果多个成员都是同一个位置标识, 那么查找就不能达到O(1)了
重点来了:
分配的这个hash值(即用于查找集合中成员的位置标识), 就是通过hash方法计算得来的, 且hash方法返回的hash值最好唯一
和数组相比, 基于hash值索引的Hash Table查找某个成员的过程就是
Step 1: 通过hash值直接找到查找目标的位置
Step 2: 如果目标位置上有多个相同hash值得成员, 此时再按照数组方式进行查找
hash方法什么时候被调用?
带着这个问题, 我们来看下面的例子
Person *person1 = [Person personWithName:kName1 birthday:self.date1];
Person *person2 = [Person personWithName:kName2 birthday:self.date2];
NSMutableArray *array1 = [NSMutableArray array];
[array1 addObject:person1];
NSMutableArray *array2 = [NSMutableArray array];
[array2 addObject:person2];
NSLog(@"array end -------------------------------");
NSMutableSet *set1 = [NSMutableSet set];
[set1 addObject:person1];
NSMutableSet *set2 = [NSMutableSet set];
[set2 addObject:person2];
NSLog(@"set end -------------------------------");
NSMutableDictionary *dictionaryValue1 = [NSMutableDictionary dictionary];
[dictionaryValue1 setObject:person1 forKey:kKey1];
NSMutableDictionary *dictionaryValue2 = [NSMutableDictionary dictionary];
[dictionaryValue2 setObject:person2 forKey:kKey2];
NSLog(@"dictionary value end -------------------------------");
NSMutableDictionary *dictionaryKey1 = [NSMutableDictionary dictionary];
[dictionaryKey1 setObject:kValue1 forKey:person1];
NSMutableDictionary *dictionaryKey2 = [NSMutableDictionary dictionary];
[dictionaryKey2 setObject:kValue2 forKey:person2];
NSLog(@"dictionary key end -------------------------------");
为了看清楚hash方法是否被调用, 我们重写hash方法如下
- (NSUInteger)hash {
NSUInteger hash = [super hash];
NSLog(@"hash = %ld", hash);
return hash;
}
打印结果如下
person1 == person2 = NO
[person1 isEqual:person2] = NO
isEqual end -------------------------------
array end -------------------------------
hash = 7809196951631946839
hash = 7809196951631946839
hash = 7809191961023760480
hash = 7809191961023760480
set end -------------------------------
dictionary value end -------------------------------
hash = 7809196951631946839
hash = 7809196951631946839
hash = 7809191961023760480
hash = 7809191961023760480
dictionary key end -------------------------------
从打印结果可以看到:
hash方法只在对象被添加至NSSet和设置为NSDictionary的key时会调用
NSSet添加新成员时, 需要根据hash值来快速查找成员, 以保证集合中是否已经存在该成员
NSDictionary在查找key时, 也利用了key的hash值来提高查找的效率
hash方法与判等的关系?
hash方法主要是用于在Hash Table查询成员用的, 那么和我们要讨论的isEqual()有什么关系呢?
为了优化判等的效率, 基于hash的NSSet和NSDictionary在判断成员是否相等时, 会这样做
Step 1: 集成成员的hash值是否和目标hash值相等, 如果相同进入Step 2, 如果不等, 直接判断不相等
Step 2: hash值相同(即Step 1)的情况下, 再进行对象判等, 作为判等的结果
简单地说就是
hash值是对象判等的必要非充分条件
如何重写自己的hash方法?
很多人在iOS开发中, 都是这么重写hash方法的
- (NSUInteger)hash {
return [super hash];
}
这样写有问题么? 带着这个问题, 我们先来看下[super hash]的值到底是什么
Person *person = [[Person alloc] init];
NSLog(@"person = %ld", (NSUInteger)person);
NSLog(@"[person1 getSuperHash] = %ld", [person getSuperHash]);
打印结果如下
person = 140643147498880
[person1 getSuperHash] = 140643147498880
由此可以看出, [super hash]返回的就是该对象的内存地址
联想到前面对hash值唯一性的要求, 使用对象的内存地址作为hash值不是很好么?
别急, 我们添加如下两个对象到NSSet中试试
Person *person1 = [Person personWithName:kName1 birthday:self.date1];
Person *person2 = [Person personWithName:kName1 birthday:self.date1];
NSLog(@"[person1 isEqual:person2] = %@", [person1 isEqual:person2] ? @"YES" : @"NO");
NSMutableSet *set = [NSMutableSet set];
[set addObject:person1];
[set addObject:person2];
NSLog(@"set count = %ld", set.count);
此时打印结果如下
[person1 isEqual:person2] = YES
set count = 2
isEqual相等的两个对象都加入到了NSSet中(set count = 2), 所以直接返回[super hash]是不正确的
那么hash方法的最佳实践到底是什么呢?
大神Mattt Thompson在Equality中给出的结论就是
In reality, a simple XOR over the hash values of critical properties is sufficient 99% of the time(对关键属性的hash值进行位或运算作为hash值)
对于上面Person类的hash方法实现如下
- (NSUInteger)hash {
return [self.name hash] ^ [self.birthday hash];
}
更多关于位运算的讨论, 参考Implementing Equality and Hashing
参考
iOS开发 之 不要告诉我你真的懂isEqual与hash!的更多相关文章
- ios开发介绍
iOS开发概述 •什么是IOS •什么是IOS开发 •为什么要选择IOS开发 •学习IOS开发的准备 1.什么是iOS •iOS是一款由苹果公司开发的操作系统(OS是Operating Sys ...
- iOS开发技巧系列---详解KVC(我告诉你KVC的一切)
KVC(Key-value coding)键值编码,单看这个名字可能不太好理解.其实翻译一下就很简单了,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值.而不需 ...
- 【IOS开发笔记02】学生管理系统
端到端的机会 虽然现在身处大公司,但是因为是内部创业团队,产品.native.前端.服务器端全部坐在一起开发,大家很容易做零距离交流,也因为最近内部有一个前端要转岗过来,于是手里的前端任务好像可以抛一 ...
- 简述 Ruby 与 DSL 在 iOS 开发中的运用
阅读本文不需要预先掌握 Ruby 与 DSL 相关的知识 何为 DSL DSL(Domain Specific Language) 翻译成中文就是:"领域特定语言".首先,从定义就 ...
- iOS开发——面试指导
iOS面试指导 一 经过本人最近的面试和对面试资料的一些汇总,准备记录这些面试题,以便ios开发工程师找工作复习之用,本人希望有面试经验的同学能和我同时完成这个模块,先出面试题,然后会放出答案. 1. ...
- 【转】iOS 开发怎么入门?
原文网址:http://www.zhihu.com/question/20264108 iOS 开发怎么入门? 请问有设计模式.内存管理方面的资料吗?最好有除了官方文档之外的其它内容,10 条评论 分 ...
- iOS开发网络篇—XML数据的解析
iOS开发网络篇—XML数据的解析 iOS开发网络篇—XML介绍 一.XML简单介绍 XML:全称是Extensible Markup Language,译作“可扩展标记语言” 跟JSON一样,也是 ...
- iOS开发中的MVC设计模式
我们今天谈谈cocoa程序设计中的 模型-视图-控制器(MVC)范型.我们将从两大方面来讨论MVC: 什么是MVC? M.V.C之间的交流方式是什么样子的? 理解了MVC的概念,对cocoa程序开发是 ...
- iOS开发:详解Objective-C runTime
Objective-C总Runtime的那点事儿(一)消息机制 最近在找工作,Objective-C中的Runtime是经常被问到的一个问题,几乎是面试大公司必问的一个问题.当然还有一些其他问题也几乎 ...
随机推荐
- 我的Android进阶之旅------>android Button上面的英文字符串自己主动大写的问题解决
今天碰到一个关于Button的问题:android Button上面的英文字符串会自己主动变成大写,执行的Android 5.1版本号,例如以下图所看到的: 图1:Button 图2:TextView ...
- BZOJ:3441 乌鸦喝水
bzoj:3441 乌鸦喝水 题目传送门 Description 一只乌鸦在自娱自乐,它在面前放了n个有魔力的水缸,水缸里装有无限的水. 他准备从第1个水缸飞到第n个水缸,共m次.在飞过一个水缸的过程 ...
- poj--1383--Labyrinth(树的直径)
Labyrinth Time Limit: 2000MS Memory Limit: 32768K Total Submissions: 4062 Accepted: 1529 Descrip ...
- zzulioj--1769--去师院的旅程:能怎么走(三)(0.0)
1796: 去师院的旅程:能怎么走(三) Time Limit: 1 Sec Memory Limit: 128 MB Submit: 404 Solved: 200 SubmitStatusWe ...
- lightoj--1294--Largest Box(三分)
Largest Box Time Limit: 2000MS Memory Limit: 32768KB 64bit IO Format: %lld & %llu Submit Sta ...
- 杂项-软件: VBA(Visual Basic for Applications)
ylbtech-杂项-软件: VBA(Visual Basic for Applications) VBA (Visual Basic宏语言) Visual Basic for Application ...
- 杂项-项目管理:WBS(工作分解结构)
ylbtech-杂项-项目管理:WBS(工作分解结构) WBS:工作分解结构(Work Breakdown Structure) 创建WBS:创建WBS是把项目 交付成果和项目工作分解成较小的,更易于 ...
- 如何在rul中添加图片
先制作要添加的ico图,如faction.ico 在<title>使用 <link rel="icon" href="../images/faction ...
- 解决Highcharts指针偏离的问题
Highcharts 在初始化3D柱状图 时 (注意:3D图形会发生偏移,2D显示正常) 如果加入 shared: true,属性,则可能会导致柱 状图 toolTip提示框偏移,解决方法是去掉或注释 ...
- 队列(FIFO)详解
写在前面的话: 一枚自学Java和算法的工科妹子. 算法学习书目:算法(第四版) Robert Sedgewick 算法视频教程:Coursera Algorithms Part1&2 本文 ...