美文网首页
iOS中的“对象同等性”(==、isEqual、hash、Com

iOS中的“对象同等性”(==、isEqual、hash、Com

作者: 半路出家的coder | 来源:发表于2018-02-07 18:58 被阅读36次

    OC中的同等性判断

    先定义一个Person类(这里我先借鉴一本资料中的例子):

    
    //  Person.h
    
    #import  <Foundation/Foundation.h>
    
    @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   <Person: 0x60800002b9a0>
    
    NSLog(@"jack in Set  %@", [set member:jack]);
    
    //打印结果:jackOne in Set  <Person: 0x60800002b680>
    
    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  <Person: 0x60800002b9a0>
    
    **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  <Person: 0x60800002b680>
    
    **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  <Person: 0x60400003bae0>**
    
    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 <Person: 0x60400003bae0>
    
    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 等等

    其实,刚开始时,我一直对使用这个协议的场景保持疑惑,在之前的工作中一直没有碰到过。直到在一个列表中用到了。

    image

    在这个页面,只会请求服务器一次,会将所有的运动项目数据都拿到。用户可以在最上面的三个筛选条件中选择,下面的列表会同时刷新。这样就带来了这样的一个需求,在某个时间段,有些运动是付费的(公司要盈利,你懂得),有些运动是新开发的,那我们当然希望这些运动能置顶,能让用户第一时间看到,便于推广。过段时间后,付费会变免费,新运动变旧运动。再推出的新付费运动,新开发运动会占据顶部位置,但是旧运动还是可以在列表中找到。根据运营的推广需要,我们需要随时能改变这些运动在列表中的顺序,甚至针对不同用户,同样一组运动,排列顺序也会不同。这个时候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}
    
    

    成功完成需求。

    相关文章

      网友评论

          本文标题:iOS中的“对象同等性”(==、isEqual、hash、Com

          本文链接:https://www.haomeiwen.com/subject/mtmazxtx.html