版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.09.15 |
前言
+ (NSUInteger)hash;
和- (BOOL)isEqual:(id)object;
都是NSObject
类中的方法,也就是说所有的类都可以调用这两个方法来实现相应的功能,他们两个的功能和深层次原理是什么呢?下面我们开始一起研究。
isEqual方法
1. isEqual方法的基本作用
这个大概大家都知道的作用就是用来比较是否相等,但是具体比较什么相等呢?还有就是和==
有什么区别呢?这里有几点需要注意:
-
==
对于基本数据类型,比较的是值是否相等;而对于对象类型,==
运算符比较的是对象的地址,也就是说判断是否是一个对象。 -
- (BOOL)isEqual:(id)object;
方法比较的是对象是否相等,不是指地址。并且非对象不能使用此方法。
下面我们看一个简单的例子。
- (void)demoEqual
{
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];
NSInteger number1 = 20;
NSInteger number2 = 20;
if (color1 == color2) {
NSLog(@"== YES");
}
else {
NSLog(@"== NO");
}
if (number1 == number2) {
NSLog(@"== YES");
}
else {
NSLog(@"== NO");
}
if ([color1 isEqual:color2]) {
NSLog(@"isEqual YES");
}
else {
NSLog(@"isEqual NO");
}
}
从上面可以看出来:
- 由于color1和color2不是一个对象,地址不一样。
-
==
判断的是是否是同一个地址,所以color1 == color2
会输出NO
,而[color1 isEqual:color2]
判断的是对象是否相等,这里显然是相等的。
2. 几种常见的isEqual方法
NSString isEqualToString
NSDate isEqualToDate
NSArray isEqualToArray
NSDictionary isEqualToDictionary
NSSet isEqualToSet
NSAttributedString -isEqualToAttributedString:
NSData -isEqualToData:
NSHashTable -isEqualToHashTable:
NSIndexSet -isEqualToIndexSet:
NSNumber -isEqualToNumber:
NSOrderedSet -isEqualToOrderedSet:
NSTimeZone -isEqualToTimeZone:
NSValue -isEqualToValue:
这里需要注意:
- 当比较上面任意类型的两个对象时,建议尽量使用上面的方法,而不建议使用isEqual方法。
3. NSArray的isEqual方法的内部实现
下面我们就是猜测一下NSArray对象调用isEqual方法的内部可能实现,实际上可能会更复杂,还是直接看代码。
@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
4. 自定义对象的isEqual内部实现
这个简单的例子是借鉴别人的,我这里只给出源码,就不自己去验证了。感兴趣的可以自己进行验证。
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;
@end
- (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;
}
上面是一个简单的例子,猜测的内部实现,实际上可能会比这复杂很多。
5. 其实你可以定义任何对象是否相等
这里说明的是,如果你重写isEqual
方法的时候,在哪个类中重写,就用那个类调用isEqual
方法,就可以随意实现是否相等。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
[self demoObject];
}
- (BOOL)isEqual:(id)object
{
return YES;
}
#pragma mark - Object Private Function
- (void)demoObject
{
UIFont *font = [UIFont systemFontOfSize:30.0];
if ([self isEqual:font]) {
NSLog(@"isEqual YES");
}
else {
NSLog(@"isEqual NO");
}
}
@end
下面看输出结果
2017-09-15 12:05:34.119 qswwqes[1996:713751] isEqual YES
这里我重写了isEqual
方法,所以控制器VC类调用这个方法,就可以和UIFont对象相等了,还是很好玩的,当然我们比并不建议这么做,这里只是玩玩而已,目的是加深大家对这个方法的理解和使用。
Hash
该方法仍然是NSObject
中的方法,是一个类方法,也就是说任何类都可以调用这个方法,返回的结果是一个NSInteger
值。
1. Hash Table
Hash
表其实是一种数据结构,我们平时在数组中查找成员的时候,就是遍历,这是一个解决方案,但是,如果数组的规模如果很大的时候,那么查找起来将会很浪费时间,有没有更好的方法呢?有,那就是借助哈希表。
哈希表的工作原理就是当成员加入到哈希表中时,会给他分配一个哈希值,标识改成员在集合中的位置,通过位置标识可以将查找的时间复杂度降低到很低的一个水平。分配的Hash值,就是用+ (NSUInteger)hash
这个类方法计算得到的,并且改方法返回的Hash值最好是唯一的。
Hash Table
不是顺序地存储元素(0,1,...,n-1)
,哈希表在存储器中分配n个位置,并且使用函数来计算该范围内的位置。 哈希函数是确定性的,并且良好的散列函数以相对均匀的分布生成值,而不会太计算昂贵。 当两个不同的对象计算相同的哈希值时,发生哈希冲突。 当这种情况发生时,散列表将从碰撞点寻找,并将新对象放置在第一个可用的位置。 随着哈希表变得更加拥挤,碰撞的可能性增加,这导致花费更多的时间来寻找可用空间(这就是具有均匀分布的哈希函数的好处)。
关于实现自定义哈希函数的最常见的误解之一就是确认结果,认为散列值必须是不同的。
下面看一下基于哈希表查找某个成员的过程。
- 通过hash值直接找到查找目标的位置。
- 如果目标位置上有多个相同hash值得成员,在按照数组模式进行查找。
2. hash方法的调用时机
我们可以重写hash方法。
- (NSUInteger)hash
{
NSUInteger hash = [super hash];
NSLog(@"hash = %ld", hash);
return hash;
}
通过重写该方法进行验证,可以发现只有NSSet
和NSDictionary
添加新的对象的时候会调用。NSSet
和NSDictionary
根据hash值查找成员来提高查找效率。
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];
}
3. hash方法和isEqual的关系
hash方法和isEqual到底是什么关系呢?简单来说就是hash值是对象判等的必要非充分条件。扩展来说,如果两个对象相等,那么他们的hash值一定相等,但是,如果两个对象的哈希值相等是不能一定推出来这两个对象是相等的。
为了优化NSSet
和NSDictionary
判断成员相等的效率,一般会如下:
- 先判断集合成员的hash值是否和目标的hash值是否相等,如果不等直接就判断不相等,如果相等进入下面步骤。
- hash值相同的情况下,再对对象进行判等,作为最终的判断结果。
参考文章
[1. Equality Written by Mattt Thompson — August 26th, 2013]
2. iOS开发 之 不要告诉我你真的懂isEqual与hash!
后记
未完,待续~~~