首页 > ios > 第8条:理解“对象等同性”这一概念(2)

第8条:理解“对象等同性”这一概念(2)

等同性判定的执行深度

  创建等同性判定方法时,需要决定是根据整个对象来判断等同性,还是仅根据其中几个字段来判断。NSArray的检测方式为先看两个数组所含对象个数是否相同,若相同,则在每个对应位置的两个对象身上调用其“isEqual:”方法。如果对应位置上的对象均相等,那么这两个数组就相等,这叫做“深度等同性判定”(deep equality)。不过有时候无须将所有数据逐个比较,只根据其中部分数据即可判明二者是否等同。
  比方说,我们假设EOCPerson类的实例是根据数据库里的数据创建而来,那么其中就可能会含有另外一个属性,此属性是“唯一标识符”(unique identifier),在数据库中用作“主键”(primary key):

@property NSUInteger identifier;

  在这种情况下,我们也许只会根据标识符来判断等同性,尤其是在此属性声明为readonly时更应该如此。因为只要两者标识符相同,就肯定表示同一个对象,因而必然相等。这样的话,无须逐个比较EOCPerson对象的每条数据,只要标识符相同,就说明这两个对象就是由同一个数据源所创建的,据此我们能够断定,其余数据也必然相同。
  是否需要在等同性判定方法中检测全部字段取决于受测对象。只有类的编写者才可以确定两个对象实例在何种情况下应判定为相等。

容器中可变类的等同性

  还有一种情况一定要注意,就是在容器中放入可变类对象的时候。把某个对象放入collection之后,就不应再改变其哈希码了。前面解释过,collection会把各个对象按照其哈希码分装到不同的“箱子数组”中。如果某对象在放入“箱子”之后哈希码又变了,那么其现在所处的这个箱子对它来说就是“错误”的。要想解决这个问题,需要确保哈希码不是根据对象的“可变部分”(mutable portion)计算出来的,或是保证放入collection之后就不再改变对象内容了。笔者将在第18条中解释为何要将对象做成“不可变的”(immutable)。这里先举个例子,此例能很好地说明其中缘由。
  用一个NSMutableSet与几个NSMutableArray对象测试一下,就能发现这个问题了。首先把一个数组加入set中:

NSMutableSet *set = [NSMutableSetnew];
NSMutableArray *arrayA = [@[@1, @2]mutableCopy];
[set addObject:arrayA];
NSLog(@"set = %@", set);
// Output: set = {((1,2))}

  现在set里含有一个数组对象,数组中包含两个对象。再向set中加入一个数组,此数组与前一个数组所含的对象相同,顺序也相同,于是,待加入的数组与set中已有的数组是相等的:

NSMutableArray *arrayB = [@[@1, @2]mutableCopy];
[set addObject:arrayB];
NSLog(@"set = %@", set);
// Output: set = {((1,2))}

  此时set里仍然只有一个对象:因为刚才要加入的那个数组对象和set中已有的数组对象相等,所以set并不会改变。这次我们来添加一个和set中已有对象不同的数组:

NSMutableArray *arrayC = [@[@1]mutableCopy];
[set addObject:arrayC];
NSLog(@"set = %@", set);
// Output: set = {((1),(1,2))}

  正如大家所料,由于arrayC与set里已有的对象不相等,所以现在set里有两个数组了:其中一个是最早加入的,另一个是刚才新添加的。最后,我们改变arrayC的内容,令其和最早加入set的那个数组相等:

[arrayC addObject:@2];
NSLog(@"set = %@", set);
// Output: set = {((1,2),(1,2))}

  set中居然可以包含两个彼此相等的数组!根据set的语义是不允许出现这种情况的,然而现在却无法保证这一点了,因为我们修改了set中已有的对象。若是拷贝此set,那就更糟糕了:

NSSet *setB = [set copy];
NSLog(@"setB = %@", setB);
// Output: setB = {((1,2))}

  复制过的set中又只剩一个对象了,此set看上去好像是由一个空set开始、通过逐个向其中添加新对象而创建出来的。这可能符合你的需求,也可能不符合。有的开发者也许想要忽略set中的错误,“照原样”(verbatim)复制一个新的出来,还有的开发者则会认为这样做挺合适的。这两种拷贝算法都说得通,于是就进一步印证了刚才提到的那个问题:如果把某对象放入set之后又修改其内容,那么后面的行为将很难预料。

  举这个例子是想提醒大家:把某对象放入collection之后改变其内容将会造成何种后果。笔者并不是说绝对不能这么做,而是说如果真要这么做,那就得注意其隐患,并用相应的代码处理可能发生的问题。

要点

  若想检测对象的等同性,请提供“isEqual: ”与hash方法。
  相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
  不要盲目地逐个检测每条属性,而是应该依照具体需求来制定检测方案。
  编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。

  1. 还没有评论
评论提交中, 请稍候...

留言


可以使用的标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Trackbacks & Pingbacks ( 0 )
  1. 还没有 trackbacks