iOS笔记:进一步认识 ==、isEqual、hash

栏目: IOS · 发布时间: 7年前

内容简介:最近在新接手的项目中进行对象比较,对同一个对象调用isEqual来比较,结果竟然是NO。猜想是对象重写了算法(算法基础还是有用的)的优点,然后快速定位到放回NO的条件。然后继续根据二分查找,最终找到self.releasetime比较的时候返回NO。原来releasetime字段为nil,使用isEqualToString比较两个为nil的对象的时候返回NO。

最近在新接手的项目中进行对象比较,对同一个对象调用isEqual来比较,结果竟然是NO。猜想是对象重写了 isEqual 方法。查看代码如下:

iOS笔记:进一步认识 ==、isEqual、hash
果然重写了 isEqual 方法,虽然方法不太严谨,没有首先判断==,代码看起来也什么大问题,但是同一个对象比较也不应该返回NO啊。看了下面一堆&&判断,难道要一个个po吗?突然想到了 二分查找

算法(算法基础还是有用的)的优点,然后快速定位到放回NO的条件。

iOS笔记:进一步认识 ==、isEqual、hash

然后继续根据二分查找,最终找到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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms

Algorithms

Sanjoy Dasgupta、Christos H. Papadimitriou、Umesh Vazirani / McGraw-Hill Education / 2006-10-16 / GBP 30.99

This text, extensively class-tested over a decade at UC Berkeley and UC San Diego, explains the fundamentals of algorithms in a story line that makes the material enjoyable and easy to digest. Emphasi......一起来看看 《Algorithms》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具