你是否真的了解OC对象相等?

你是否真的了解OC对象相等?



  比较两个对象是否相等是一个常用的功能。==操作符比较的不是对象,而是两个指针本身,一般来说这不是咱们所想要的。要想判断对象是否相等,应该使用 NSObject 协议中声明的isEqual:方法来判断。一般来说,两个类型不同的对象总是不相等的。

  如果你已经有过一些 OC 的编码经验的话,你一定自定义过isEqual:方法。那么你是碰到过一些奇葩的问题呢?咱们先来看一段代码:

@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@end

@implementation Person

- (BOOL)isEqual:(id)object {
  if (self == object) return YES;
  if (![object isKindOfClass:[Person class]]) return NO;

  return [self.name isEqualToString:[object name]];
}

- (id)copyWithZone:(NSZone *)zone {
  Person *person = [[Person allocWithZone:zone] init];
  person.name = self.name;
  return person;
}
@end

- (void)test {
  Person *person = [[Person alloc] init];
  person.name = @"Lili";

  NSDictionary *dic = @{person: @1};
  Person *personCopy = [person copy];

  id object = [dic objectForKey:personCopy];
  NSLog(@"%@", [person isEqual:personCopy] ? @"YES" : @"NO"); // 输出:YES
  NSLog(@"%@", object);  // 输出:(null)
}

  这段代码定义了类:Person,实现了isEqual:方法,并实现了NSCopying协议。在测试中,以 Person 对象作为 key放到了字典中。再以 person 对象 copy 出一个新的对象:personCopy。但用这个 personCopy 作为 key 从字典中去取相应的值时,居然没有取到,输出为(null)。但咱们判断过 person 与 personCopy 对象呀:[person isEqual:personCopy],而且它是的确是相等的。

  那么问题出在哪了?系统问题?不可能。咱们先看看NSDictionary中对key的要求:

  在字典中,keys都是唯一的。也就是说,在一个字典中不会有两个key相等(通过isEqual:来判断)。一般来说,一个key可以是任何实现了NSCopying协议的对象

  NSCopying协议只定义了copyWithZone:方法,咱们也实现了,不是这个问题。若非问题出在isEqual:上?这个方法声明在NSObject协议中。来看看这个方法其中一段说明:

  如果两个对象是相等的,那么它们就必须有相同的哈希码(hash value)。如果你的自定义类型定义了isEqaul:方法,并打算将其实例需要放到集合中,这点尤为重要。你应该确保在定义了isEqual:方法的同时,也定义hash方法。

  也就是说,NSObject协议中有两个用于判断等同性的方法:

  - (BOOL)isEqual:(id)object;
  - (NSUinteger)hash;

  NSObject类对这两个方法的默认实现是:当且仅当其“指针值”完全相等时,这两个对象才相等。若想在自定义类中正确实现这些方法,就必须先理解其约定。如果isEqual:方法判定两个对象相等,那么其hash方法也必须返回同一值。但是,如果两个对象的hash方法返回同一个值,那么isEqual:方法未必会认为两者相等。

  哦,原来是这样呀,看来问题就是出在这里了。那么咱们写一个hash方法出来看看:

// Person.m
- (NSUInteger)hash {
  return [self.name hash];
}

// Test
- (void)test {
  Person *person = [[Person alloc] init];
  person.name = @"Lili";

  NSDictionary *dic = @{person: @1};
  Person *personCopy = [person copy];

  id object = [dic objectForKey:personCopy];
  NSLog(@"%@", [person isEqual:personCopy] ? @"YES" : @"NO"); // 输出:YES
  NSLog(@"%@", object);  // 输出:1
}

  OK,这回没有问题了。

  

  结论:实现isEqual:方法的同时,要实现hash方法!

时间: 2024-08-03 09:14:11

你是否真的了解OC对象相等?的相关文章

ARC下OC对象和CF对象之间的桥接(bridge)

在开发iOS应用程序时我们有时会用到Core Foundation对象简称CF,例如Core Graphics.Core Text,并且我们可能需要将CF对象和OC对象进行互相转化,我们知道,ARC环境下编译器不会自动管理CF对象的内存,所以当我们创建了一个CF对象以后就需要我们使用CFRelease将其手动释放,那么CF和OC相互转化的时候该如何管理内存呢?答案就是我们在需要时可以使用__bridge,__bridge_transfer,__bridge_retained,具体介绍和用法如下

黑马程序员-oc对象的内存管理

oc没有java的垃圾回收机制,所以对象的内存释放很重要,基本数据类型,我们不用理会,编译器会处理: oc的每个对象内部都由一个计数器,用来记录当前有几个指针在指向该对象:当计数器为0时该对象会从内存中释放: 相关方法和概念: 1:retain:对象方法,调用该对象方法,计数器+1,有返回值,返回对象本身: 2:release:没有返回值,计数器-1: 3:retainCount:获取当前计数器的值: 4:dealloc:当对象被回收时,就会调用该方法,覆盖该方法时一定要调用[super dea

OC对象给分类加入属性

OC对象中不能给分类加入属性.可是在实际开发中.常常为了更好的性能须要给分类加入属性,那么 加入的属性不能有默认的成员变量.须要我们自己实现set和get方法,要用到执行时 例如以下: #import <objc/runtime.h> //执行时的关联对象.动态加入属性 const void *URLStringKey = "URLStringKey"; //set方法 - (void)setUrlStr:(NSString *)urlStr { objc_setAssoc

OC 对象和匿名对象

OC 对象和匿名对象 对象和匿名对象的定义: 当new出一个对象时,如果用一个指针接收这个对象,那么这个指针通常被称为对象. 如果new出的对象,不用指针接收,那么这个对象就称为匿名对象. #import <Foundation/Foundation.h> #import "Iphone.h" #import "Person.h" int main(int argc, const char * argv[]) { //1.通过指针访问对象 Iphone

OC对象给分类添加属性

OC对象中不能给分类添加属性,但是在实际开发中,经常为了更好的性能需要给分类添加属性,那么 添加的属性不能有默认的成员变量,需要我们自己实现set和get方法,要用到运行时 如下: #import <objc/runtime.h> //运行时的关联对象,动态添加属性 const void *URLStringKey = "URLStringKey"; //set方法 - (void)setUrlStr:(NSString *)urlStr { objc_setAssocia

OC对象与Core Foundation对象的转换

OC对象使用了ARC,自动释放内存,但是CF中的对象没有ARC,必须要手动进行引用计数和内存释放. 两者对象之间的互相转换有三种形式: 1.__bridge: 直接转换,部改变对象的持有状况: id obj = [NSObject new]; cfObject = (_bridge cfObject) obj; //不改变对象持有状况,cf对象不能获得obj的引用. id obc = (__bridge id)cfObject; //但obc是强引用,是被__strong修饰的,必定获得cf对象

OC对象的本质

OC对象的本质 平时编写的Object-C代码,底层实现其实都是C/C++代码. 所以Objective-C的面向对象都是基于C/C++的数据结构实现的,OC对象内部可以容纳不同数据类型的数据,因此可以推断OC对象的底层数据结构就是结构体. 为了进一步研究OC对象的本质,我们使用苹果Xcode内置的LLVM编译器前端clang中-rewrite-objc 将我们的OC代码转为C/C++代码. clang -rewrite-objc main.m -o main.cpp 由于不同平台支持的代码肯定

ios开发:OC对象的内存分析

最近要开始准备找实习单位了,做做笔试题,看看各位大神的面试经历,发现自己要学习的东西真的还有很多,虽然也做过几个的项目,但是真正拿过笔试题一看,才发现自己对基础这方面的东西,确实有点忽视了,所以最近开启恶补模式. 这几天在研究ios内存分析这一块,才发现自己原来忽视了这么多重要的东西,在这里和大家分享一下. 先来看一段代码: NSString* s;s = [[NSString alloc] initWithString:@"hello ios"]; 这是一段最简单的字符串定义,但是看

oc对象函数什么时候返回值类型使用instancetype

oc中定义对象函数时经常会返回本类类型的对象,此时返回值类型用instancetype或者本类对象*都可以,什么区别呢? 其实主要区别在返回值是不是self并且有继承 如果返回值是self并且作为父类,那么返回值最好写成instancetype 举例说明: 父类的声明 @interface Father : NSObject @property(readwrite,nonatomic,assign)NSInteger item; //元素自增 为了比较自增返回类型定为instancetype -