OC中的同等性判断
先定义一个Person类(这里我先借鉴一本资料中的例子):
// Person.h
#import
@interface Person : NSObject
@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *lastName;
@property(nonatomic, assign) NSUInteger age;
@end
// Person.m
#import "Person.h"
@implementation Person
-(BOOL) isEqual:(id)object {
if(self== object)returnYES;
if([self class] != [object class]) return NO;
Person*otherPerson = (Person*)object;
if (![_firstName isEqualToString:otherPerson.firstName]) {
return NO;
}
if (![_lastName isEqualToString:otherPerson.lastName]) {
return NO;
}
if(_age != otherPerson.age) {
return NO;
}
return YES;
}
@end
用“==”判断
Person *jack = [[Person alloc] init];
jack.firstName=@"jack";
jack.lastName=@"jack";
jack.age= 10;
Person*jackOne = [[Person alloc] init];
jackOne.firstName=@"jack";
jackOne.lastName=@"jack";
jackOne.age= 10;
BOOL equalA = (jack == jackOne);
// 打印结果:equalA = 0
NSLog(@"equalA = %d",equalA);
在这里,创建了两个对象,分别是jack和jackOne,它们的firstName、lastName和age都是赋相同的值,但是在使用“==”时,判断结果为NO。“==”该操作比较的是两个指针的本身,而不是所指对象,指针是栈内存中的一块数据, 对象是在堆内存中,指针保存着所指对象在内存中的位置.既然说到了对象在内存中的位置,那就打印看下这两个对象的地址。
// **打印结果:jack对象的地址:0x60c00003af80, jackOne对象的地址0x60c00003ad20**
NSLog(@"jack对象的地址:%p, jackOne对象的地址%p",jack,jackOne);
很显然,这两个对象的内存地址也不同,就算“==”不是比较指针的,它们还是不相等。
用“isEqual”判断
在Person类中,我重写了NSObject协议中的“isEqual”方法,那看下用“isEqual”来判断对象同等,是什么结果.
BOOL equalB = [jack isEqual:jackOne];
// 打印结果:equalB = 1
NSLog(@"equalB = %d",equalB);
因为在重写的方法中,是通过三个属性的相等判断,来决定对象的同等性。jack和jackOne的属性值正好都相等,所以虽然内存地址不同,分明是两个对象,所以仍然判断“同等”。也许看到这里,你会觉得只要重写了“isEqual”, 可以将两个对象“同等”,其实还差了一步,请看下面。
NSMutableSet *set = [NSMutableSet set];
[set addObject:jack];
[set addObject:jackOne];
//**jack对象的地址:0x60800002b9a0, jackOne对象的地址0x60800002b680
NSLog(@"jack对象的地址:%p, jackOne对象的地址%p",jack,jackOne);
//打印结果:jack in Set
NSLog(@"jack in Set %@", [set member:jack]);
//打印结果:jackOne in Set
NSLog(@"jackOne in Set %@", [set member:jackOne]);
//打印结果:set.count = 2
NSLog(@"set.count = %lu",(unsigned long)set.count);
以之前的结论,jackOne是无法加入set。但事实是jackOne也成功加入Set,这也证明了jack和jackOne仍然不“同等”。
那如何才能将jack和jackOne同等呢?
除了重写“isEqual”,还得重写“hash”
在集合中,加入对象之前会判断对象同等性,实质是判断集合中已存在的对象与待加入对象的hash值是否相等,再判断“isEqual”。hash值不同了,就不再进行“isEqual”判断,但是有些对象的hash值在一些巧合的条件下,还是会相等,这个时候就开始第二层判断,用“isEqual”。在上面的例子中,第一层hash值的判断结果就不同,所以根本不会再进行第二层“isEqual”判断。我将Person.m的代码修改一下,验证一下。
// Person.m
#import "Person.h"
@implementation Person
-(BOOL) isEqual:(id)object {
NSLog(@"isEqual判断");
if(self == object) return YES;
if([self class] != [object class]) return NO;
Person*otherPerson = (Person*)object;
if (![_firstName isEqualToString:otherPerson.firstName]) {
return NO;
}
if (![_lastName isEqualToString:otherPerson.lastName]) {
return NO;
}
if(_age != otherPerson.age) {
return NO;
}
return YES;
}
// 重写了hash方法,增加了打印,但是hash值是并没有改变,其实用运行时方法也可以实现该功能
- (NSUInteger) hash {
NSLog(@"hash判断");
return [super hash];
}
@end
这个时候,我再重新跑一遍程序,看下结果:
**2018-02-07 17:32:07.931454+0800 JustTry[10536:4321392] jack in Set
**2018-02-07 17:32:07.931644+0800 JustTry[10536:4321392] hash判断
**2018-02-07 17:32:07.931808+0800 JustTry[10536:4321392] jackOne in Set
**2018-02-07 17:32:07.932437+0800 JustTry[10536:4321392] set.count = 2
打印结果显示,set在先加入了jack对象后,准备加入jackOne之前进行了hash判断,而且“isEqual”方法没有走!hash不同,后面的方法当然就不用走了。如果想让jackOne无法加入set呢?那就像下面一样改下。
-(NSUInteger)hash {
NSLog(@"hash判断");
// 这个时候,所有Person实例的hash值都相同了
return 666;
}
再打印下,看结果。
2018-02-07 17:41:57.647062+0800 JustTry[10673:4394075] jack对象的地址:0x60400003bae0, jackOne对象的地址0x60400003bc40**
2018-02-07 17:41:57.649206+0800 JustTry[10673:4394075] jack in Set **
2018-02-07 17:41:57.649492+0800 JustTry[10673:4394075] hash判断
2018-02-07 17:41:57.650034+0800 JustTry[10673:4394075] isEqual判断
2018-02-07 17:41:57.650171+0800 JustTry[10673:4394075] jackOne in Set
2018-02-07 17:41:57.650354+0800 JustTry[10673:4394075] set.count = 1
打印结果显示,jack是加入了set中,而jackOne没有。判断过程是:先判断了hash,hash不同再判断了isEqual。虽说代码是[set member:jackOne],传入的是jackOne,但是对set来说它们是同一个对象,打印的地址显示的是jack的地址而不是jackOne。
综上所述,为了在项目中能保证自定义类的实例能按你的要求来判断同等性,除了重写“isEqual”,记得要重写“hash”。其实重写的“hash”方法中,像我这样指定个数字并不好,相对而言较好的方法是将所有属性异或。
swift中的同等性判断
swift中,如果自定义类不是继承自NSObject,想比较对象之间的大小、相等,那就需要遵守Comparable协议了,重写下面的相关方法.
publicstaticfunc<(lhs:Self, rhs:Self) ->Bool, publicstaticfunc<=(lhs:Self, rhs:Self) ->Bool, publicstaticfunc>=(lhs:Self, rhs:Self) ->Bool, publicstaticfunc>(lhs:Self, rhs:Self) ->Bool 等等
其实,刚开始时,我一直对使用这个协议的场景保持疑惑,在之前的工作中一直没有碰到过。直到在一个列表中用到了。
在这个页面,只会请求服务器一次,会将所有的运动项目数据都拿到。用户可以在最上面的三个筛选条件中选择,下面的列表会同时刷新。这样就带来了这样的一个需求,在某个时间段,有些运动是付费的(公司要盈利,你懂得),有些运动是新开发的,那我们当然希望这些运动能置顶,能让用户第一时间看到,便于推广。过段时间后,付费会变免费,新运动变旧运动。再推出的新付费运动,新开发运动会占据顶部位置,但是旧运动还是可以在列表中找到。根据运营的推广需要,我们需要随时能改变这些运动在列表中的顺序,甚至针对不同用户,同样一组运动,排列顺序也会不同。这个时候Comparable协议的几个方法就派上用场了。在我的项目中,后台给了个“sort”字段,我会根据sort字段来排序,值越大,排的越前,每个运动的model我定义为ProjectModel,下面贴一点儿在model中的代码:
static func <(lhs: ProjectModel, rhs: ProjectModel) -> Bool {
return lhs.sort < rhs.sort
}
static func ==(lhs: ProjectModel, rhs: ProjectModel) -> Bool {
return lhs.sort == rhs.sort
}
在其他需要排序的地方:
// itemMap是没有排序的数组,arrayForRanked是排序后的数组
self.arrayForRanked = self.itemMap.sorted{$0>$1}
成功完成需求。