内容简介:最近在新接手的项目中进行对象比较,对同一个对象调用isEqual来比较,结果竟然是NO。猜想是对象重写了算法(算法基础还是有用的)的优点,然后快速定位到放回NO的条件。然后继续根据二分查找,最终找到self.releasetime比较的时候返回NO。原来releasetime字段为nil,使用isEqualToString比较两个为nil的对象的时候返回NO。
最近在新接手的项目中进行对象比较,对同一个对象调用isEqual来比较,结果竟然是NO。猜想是对象重写了 isEqual 方法。查看代码如下:
isEqual 方法,虽然方法不太严谨,没有首先判断==,代码看起来也什么大问题,但是同一个对象比较也不应该返回NO啊。看了下面一堆&&判断,难道要一个个po吗?突然想到了
二分查找
算法(算法基础还是有用的)的优点,然后快速定位到放回NO的条件。
然后继续根据二分查找,最终找到self.releasetime比较的时候返回NO。原来releasetime字段为nil,使用isEqualToString比较两个为nil的对象的时候返回NO。
(lldb) po (BOOL)([self.releasetime isEqualToString:info.releasetime]) NO (lldb) 复制代码
解决办法:
场景一:针对当前的场景,只需要在的最顶部添加下面判断就可以了 if (self == object) return YES;
场景二:但是一些其他场景,我们确实会遇到比较两个地址不一样,但是数据一样(并且对象有的属性确实为 nil )的场景。这个时候我们可以为 NSString 等基本数据类型添加 category ,然后重写 isEqualToString: 方法,如果要比较的两个对象都是 nil 则返回 YES 。但是如果要比较的两个对象都不是nil而且 length 也相同,难道我还要重写一个方法进行遍历比较吗?能不能直接用原来的 isEqualToString: 方法,但是 category 中不能调用 super 方法,再说分类也不是子类。此时我们就需要利用 runtime 遍历方法列表找到原来的方法,然后调用一下返回结果就可以了。后来发现,想多了,给nil发送消息返回的始终是NO。最后在分类中实现一个类方法判断一下就可以了。
#import "NSString+isEqual.h"
@implementation NSString (isEqual)
+(void)load {
NSLog(@"结果1 = %@", [nil isEqualToString:nil] ? @"YES" : @"NO");
NSLog(@"结果2 = %@", [NSString isString:nil EqualToString:nil] ? @"YES" : @"NO");
}
+(BOOL)isString:(NSString *)aString EqualToString:(NSString *)bString {
if (aString == nil && bString == nil ) {
return YES;
}
return [aString isEqualToString:bString];
}
@end
复制代码
思考一:==与isEqual的区别?
基本类型 对象类型 isEqual
思考二:isEqual的默认实现
isEqual方法是NSObject中声明的,默认实现就是简单的比较对象地址。
@implementation NSObject (Approximate)
- (BOOL)isEqual:(id)object {
return self == object;
}
@end
复制代码
思考三:自定义继承NSObject的子类需要重写的几个方法
-(BOOL)isEqual:(id)object -(NSUInteger)hash -(id)copyWithZone:(NSZone *)zone
注意: 使用OC自定义的类实例作为NSDictionary的key的话,需要实现NSCoping协议,如果不实现的话,向dictionary中添加数据时会报警告:Sending 'Coder *__strong to parameter of incompatible type 'id _Nonnull',在运行的时候,在setObject forKey函数这里直接崩溃
思考四:Foundation中NSObject的子类已经定义的自己的isEqual
-isEqualToAttributedString: -isEqualToData: -isEqualToDate: -isEqualToDictionary: -isEqualToHashTable: -isEqualToIndexSet: -isEqualToNumber: -isEqualToOrderedSet: -isEqualToSet: -isEqualToString: -isEqualToTimeZone: -isEqualToValue:
思考五:重写的isEqual什么时候调用
- 1、NSArray的
containsObject:和removeObject:方法都是使用了isEqual来判断成员是否相等的 - 2、当hash方法设计不是很完美的时候,两个对象返回一样的hash值,就会调用
isEqual继续判断是否为两个相同对象
思考六:为什么需要hash
目的:为了提高查找的速度
- 1、在数组未 排序 的情况下, 查找的时间复杂度是O(n)。
- 2、当成员被加入到Hash Table中时, 会给它分配一个hash值, 以标识该成员在集合中的位置,通过这个位置标识可以将查找的时间复杂度优化到O(1), 当然如果多个成员都是同一个位置标识, 那么查找就不能达到O(1)了。
- 3、分配的这个hash值(即用于查找集合中成员的位置标识), 就是通过hash方法计算得来的, 且hash方法返回的hash值最好唯一
和数组相比, 基于hash值索引的Hash Table查找某个成员的过程就是
- Step 1: 通过hash值直接找到查找目标的位置
- Step 2: 如果目标位置上有多个相同hash值得成员, 此时再按照数组方式进行查找
思考七:hash什么时候调用
HashTable是一种基本数据结构,NSSet和NSDictionary都是使用HashTable存储数据的,因此可以可以确保他们查询成员的速度为O(1)。而NSArray使用了顺序表存储数据,查询数据的时间复杂度为O(n)。
- 1、hash方法会在对象被添加到NSSet中的时候调用
Coder* coder1 = [Coder initWith:@"C++" level:@"11"]; Coder* coder2 = [Coder initWith:@"C++" level:@"11"]; Coder* coder3 = [Coder initWith:@"C++" level:@"17"]; NSSet* coderSet = [NSSet setWithObjects:coder1, coder2, coder3, nil]; NSLog(@"coderSet.count = %ld", coderSet.count); 复制代码
- 2、hash方法会在对象被用作NSDictionary的key的时候调用
Coder* coder1 = [Coder initWith:@"C++" level:@"11"]; Coder* coder2 = [Coder initWith:@"C++" level:@"11"]; Coder* coder3 = [Coder initWith:@"C++" level:@"17"]; NSMutableDictionary* coderDic2 = [NSMutableDictionary dictionary]; [coderDic2 setObject:@"1" forKey:coder1]; [coderDic2 setObject:@"2" forKey:coder2]; [coderDic2 setObject:@"3" forKey:coder3]; NSLog(@"coderDic2.count = %ld", coderDic2.count); 复制代码
思考八:hash和isEqual的关系
isEqual
思考九:重写hash的原则
- hash 方法不能返回一个常量。因为使用了这个值作为 hash 表的 key,会导致 hash 表 100%的碰撞。
- 一个对象实例的 hash 计算结果应该是确定的。hash方法设计的好坏直接影响查找的效率。
贴代码
.h文件
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Coder : NSObject<NSCopying> @property (nonatomic, strong) NSString *language; @property (nonatomic, strong) NSString *level; +(instancetype)initWith:(NSString*)language level:(NSString*)level; @end NS_ASSUME_NONNULL_END 复制代码
.m文件
#import "Coder.h"
@implementation Coder
+(instancetype)initWith:(NSString*)language level:(NSString*)level {
Coder* coder = [[Coder alloc] initWith:language level:level];
return coder;
}
-(instancetype)initWith:(NSString*)language level:(NSString*)level {
self.language = language;
self.level = level;
return self;
}
// 对象用作NSSDictionary的key必须实现
-(id)copyWithZone:(NSZone *)zone {
Coder* coder = [[Coder allocWithZone:zone] initWith:self.language level:self.level];
return coder;
}
-(BOOL)isEqual:(id)object {
NSLog(@"func = %s", __func__);
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
// object == nil 在此返回NO
return NO;
}
return [self isEqualToCoder:object];
}
-(BOOL)isEqualToCoder:(Coder*)object {
// isEqualToString 需要使用分类重写一下,否则 [nil isEqualToString:nil]会返回NO
if (![self.language isEqualToString:object.language]) {
return NO;
}
if (![self.level isEqualToString:object.level]) {
return NO;
}
return YES;
}
-(NSUInteger)hash {
BOOL isCareAddress = YES;
NSUInteger hashValue = 0;
if (isCareAddress) {
// 如果期望对地址不同、但是内容相同的对象做区分
hashValue = [super hash];
// 结果:两个地址不同,但是内容相同的对象添加到NSMutableSet中,NSMutableSet的个数返回的是2
}
else {
// 不关心地址是否相同,只对内容进行区分(对关键属性的hash值进行按位异或运算作为hash值)
hashValue = [self.language hash] ^ [self.level hash];
// 结果:两个地址不同,但是内容相同的对象添加到NSMutableSet中,NSMutableSet的个数返回的是1
}
NSLog(@"func = %s, hashValue = %ld", __func__, hashValue);
return hashValue;
}
@end
复制代码
调用
#import "HashViewController.h"
#import "Coder.h"
@interface HashViewController ()
@end
@implementation HashViewController
- (void)viewDidLoad {
[super viewDidLoad];
Coder* coder1 = [Coder initWith:@"C++" level:@"11"];
Coder* coder2 = [Coder initWith:@"C++" level:@"11"];
Coder* coder3 = [Coder initWith:@"C++" level:@"17"];
NSArray* coderList = @[coder1, coder2, coder3];
NSLog(@"array-containsObject-coder1-start");
[coderList containsObject:coder1];
NSLog(@"array-containsObject-coder1-end");
NSLog(@"array-containsObject-coder2-start");
[coderList containsObject:coder2];
NSLog(@"array-containsObject-coder2-end");
NSLog(@"array-containsObject-coder3-start");
[coderList containsObject:coder3];
NSLog(@"array-containsObject-coder3-end");
NSLog(@"coderList.count = %ld", coderList.count);
NSSet* coderSet = [NSSet setWithObjects:coder1, coder2, coder3, nil];
NSLog(@"coderSet.count = %ld", coderSet.count);
NSDictionary* coderDic = @{@"1":coder1, @"2":coder2, @"3":coder3};
NSLog(@"coderDic.count = %ld", coderDic.count);
NSMutableDictionary* coderDic2 = [NSMutableDictionary dictionary];
[coderDic2 setObject:@"1" forKey:coder1];
[coderDic2 setObject:@"2" forKey:coder2];
[coderDic2 setObject:@"3" forKey:coder3];
NSLog(@"coderDic2.count = %ld", coderDic2.count);
}
@end
复制代码
以上所述就是小编给大家介绍的《iOS笔记:进一步认识 ==、isEqual、hash》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Workerman学习笔记(一)初步认识
- JavaSe笔记02-添加判断和字符char的认识
- iOS开发学习笔记:对MVC、MVVM建立认识
- 认识认识LVS负载均衡集群
- 认识一下 SVG
- Netty初步认识
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
拆掉互联网那堵墙
庄良基 / 经济日报出版社 / 2014-6 / 25.80
都在说道互联网、说道电子商务、说道移动APP、说道微信、说道互联网金融......我们该如何认识互联网?中小微企业该如何借力互联网?互联网很神秘吗?很高深莫测吗? 其实互联网并没有什么神秘的,也没有什么高深莫测的!互联网无非是人类发明的工具而已,既然是工具,我们就一定可以驾驭和使用它。既然可以双重使用,就理当让所有有人都容易掌握并轻松驾驭。 互联网离我们很远吗?互联网界的成功故事都是那......一起来看看 《拆掉互联网那堵墙》 这本书的介绍吧!