美文网首页
读EffectiveObjective-C2.0(第八条)

读EffectiveObjective-C2.0(第八条)

作者: LazyLoad | 来源:发表于2020-11-11 09:01 被阅读0次

    第八条:理解“对象等同性”这一概念

    • 根据等同性来比较对象是一个非常有用的功能。使用==操作符比较未必就得到正确的结果,因为该操作符比较的是两个指针本身,而不是其指向的对象。应该使用NSObject协议中声明的isEqual:方法来判断两个对象的等同性。
    • 一般来说,两个类型不同的对象总是不相等的。
    • 某些对象提供了自己的等同性判定的方法,如果知道两个对象属于同一个类,就可以使用这种方法。
    NSString *foo = @"Badger 123";
    NSString *bar = [NSString stringWithFormat:@"Badger %i", 123];
    NSLog(@"%d", foo == bar);       // false
    NSLog(@"%d", [foo isEqual:bar]); // true
    NSLog(@"%d", [foo isEqualToString:bar]); // true
    

    从上述代码可以看出==和等同性判断的差别。NSString类实现了一个自己独有的等同性判断的方法,必须传递一个字符串对象作为参数。比isEqual:方法速度快,而isEqual:方法还要执行额外的步骤。因为它不知道接收参数的对象类型。

    • NSObject协议中有两个方法用于等同性判断:

      • - (Bool)isEqual:(id)object
      • -(NSUinteger)hash;
    • NSObject类对这两个方法的默认实现是:当且仅当其指针值完全相等时,这两个对象才相等。(下面代码参考GNUstep)

    - (BOOL) isEqual: (id)anObject
    {
      return (self == anObject);
    }
    
    - (NSUInteger) hash
    {
      /*
       *  malloc() must return pointers aligned to point to any data type
       */
    #define MAXALIGN (__alignof__(_Complex long double))
    
      static int shift = MAXALIGN==16 ? 4 : (MAXALIGN==8 ? 3 : 2);
    
      /* We shift left to lose any zero bits produced by the
       * alignment of the object in memory.
       */
      return (NSUInteger)((uintptr_t)self >> shift);
    }
    
    • 如果想判断自定义对象的等同性,就要重写这两个方法。并且需要遵循一个约定:如果isEqual方法判断两个对象相等,那么其hash方法也必须返回同一个值。但是hash方法返回同一个值,isEqual方法未必认为两个对象相等。

    • EOCPerson类举例,我们约定,如果两个EOCPerson的所有字段均相等,那么这两个对象相等。

    // EOCPerson.h
    @interface EOCPerson : NSObject
    @property (nonatomic, copy) NSString *firstName;
    @property (nonatomic, copy) NSString *lastName;
    @property (nonatomic, assign) NSUInteger age;
    @end
      
    // EOCPerson.m
    - (BOOL)isEqual:(id)object {
        if (self == object) { // 同一个对象返回YES
            return YES;
        }
        
        if ([self class] != [object class]) { // 不是同一个类型,返回NO
            return NO;
        }
        
        EOCPerson *otherPerson = (EOCPerson *)(object);
        if (![_firstName isEqualToString:otherPerson.firstName]) { // 名字不一样,返回NO
            return NO;
        }
        
        if (![_lastName isEqualToString:otherPerson.lastName]) { // 姓不一样,返回NO
            return NO;
        }
        
        if (_age != otherPerson.age) { // 年龄不一样,返回NO
            return NO;
        }
        return YES;
    }
    
    • hash方法,根据等同性的约定:两个对象相等,那么他们的hash值必须相等,但是,hash值相同的对象不一定是相等的对象。
    • 第1种实现方案:直接返回一个固定个的值
    - (NSUInteger)hash {
        return 1337;
    }
    

    如果这么写,在collection(代表Array、Dictionary、Set等数据结构)中使用会产生性能问题,因为collection在检索哈希表时,会用对象的哈希值作为索引。加入某个collection是用set实现的,那么set可能会根据哈希值把对象分装到不同数组,在向set添加对象的时候,要根据哈希值找到与之相关的数组,依次检查其中的各个元素,看数组中已有的对象是否将要添加的新对象相等。如果相等说明对象已经在set里面了。如果对象都返回相同的哈希值,那么在set中已有100000个对象的情况下,若是继续向其中添加新对象,最坏情况下,需要把100000个对象都遍历一遍。

    • 第2中实现方案:以实例变量的值为基础,创建字符串,返回字符串的hash值。这种做法符合约定。但是每次都要用额外的开销去创建一个字符串对象。
    - (NSUInteger)hash {
        NSString *stringToHash = [NSString stringWithFormat:@"%@:%@:%i", _firstName, _lastName, _age];
        return [stringToHash hash];
    }
    
    • 第3中方案:分别生成哈希值,最后进行位运算,返回哈希值。这种做法可以保持高效,还可以使生成的哈希值在一定范围内。
    - (NSUInteger)hash {
        NSUInteger firstNameHash = [_firstName hash];
        NSUInteger lastNameHash = [_lastName hash];
        NSUInteger ageHash =_age;
        return firstNameHash ^ lastNameHash ^ ageHash;
    }
    

    特定类所具有的等同性判定方法

    • 除了NSString类之外,NSArrayNSDictionary类也具有特殊的等同性判定方法。前者为isEqualToArray:,后者为isEqualToDictionay:,如果和其相比较的对象不是数组或字典,那么就会抛出异常。
    • 如果经常需要判断某一种类型的等同性,可以自己创建一个等同性判定的方法,这样无需检测类型,提高速度,代码更加易读。
    • 自己编写等同性判定方法,同样需要重写isEqual:方法,如果两者是同一类型,就调用我们自己的判定方法,否则调用父类的方法去判断。
    - (BOOL)isEqualToPerson:(EOCPerson *)person {
        if (self == person) {
            return YES;
        }
        
        if (![_firstName isEqualToString:person.firstName]) {
            return NO;
        }
        
        if (![_lastName isEqualToString:person.lastName]) {
            return NO;
        }
        
        if (_age != person.age) {
            return NO;
        }
        
        return YES;
    }
    
    - (BOOL)isEqual:(id)object {
        if ([self class] == [object class]) {
            return [self isEqualToPerson:(EOCPerson *)object];
        } else {
            return [super isEqual:object];
        }
    }
    

    等同性判定的执行深度

    • 创建等同性方法时,需要决定是根据整个对象来判断等同性,还是仅根据其中几个字段来判断。NSArray的检测方式为先看2个数组所含对象的个数是否相同,若相同,则在每个对应位置的2个对象身上来调用isEqual:方法。如果对应位置的对象均相等,那么这2个数组就是相等的,这叫做深度等同性判定。有些时候,无需将所有数据逐个比较,只根据其中部分数据比较即可。
    • 举例:假设EOCPerson的数据来源于数据库,那么其中可能包含一个属性,就是数据库中的主键,也是唯一标识。在这种情况下,只要是唯一标识是相同的,就表示肯定是同一个对象。这样就不需要比较EOCPerson的每个属性是不是相同的了。

    容器中可变类的等同性

    • 在容器中放入可变对象的时候,一定要注意。把某个对象放入collection之后,就不应再改变其哈希值。collection会把每个对象按照其哈希值分装到不同的箱子数组中。如果某个对象在放入箱子之后哈希值又变了。那么其所处的这个箱子对它来说就是错误的。要想解决这个问题,需要保证哈希值不是根据对象的可变部分计算出来的,或是保证放入collection之后就不再改变对象内容了。
    • 举个例子:用一个NSMutableSet和几个NSMutableArray对象测试,首先把一个数组添加到set
    NSMutableSet *set = [NSMutableSet set];
    NSMutableArray *arrayA = @[@1, @2].mutableCopy;
    [set addObject:arrayA];
    NSLog(@"set = %@", set);
    /* set = {(
    (1,2)
    )}
    */
    
    • set里面有一个数组对象,数组中有两个对象。再向set中添加一个数组,此数组与前一个数组所含的对象相同,顺序也相同。
    NSMutableArray *arrayB = @[@1, @2].mutableCopy;
    [set addObject:arrayB];
    NSLog(@"set = %@", set);
    /* set = {(
    (1,2)
    )}
    */
    
    • set里面仍然只有一个对象,因为刚加入的数组与已有的数组相等的,所以set不会变。此时,我们添加一个和set已有数组不一样的数组。
    NSMutableArray *arrayC = @[@1].mutableCopy;
    [set addObject:arrayC];
    NSLog(@"set = %@", set);
    /*
    set = {(
            (1),
            (1,2)
    )}
    */
    
    • set此时有两个数组。接下来,我们改变arrayC的内容,让其和之前加入的数组相等。
    [arrayC addObject:@2];
    NSLog(@"set = %@", set);
    /*
    set = {(
            (1,2),
            (1,2)
    )}
    */
    
    • 从打印结果来看,set中有2个完全相同的数组。但是set的语义是不允许重复出现的,因为我们修改了set中的对象。如果我们此时拷贝set
    NSSet *setB = [set copy];
    NSLog(@"setB = %@", setB);
    /*
    setB = {(
            (1,2)
    )}
    */
    
    • 复制过的set又变成了只有一个数组对象。这样的代码就很容易产生很难预料的结果和隐患。

    相关文章

      网友评论

          本文标题:读EffectiveObjective-C2.0(第八条)

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