NSObject类的实例方法: - (BOOL)isEqual:(id)object
主要是根据对象的内存地址来判断两个对象是否相等,这里与 ==
效果相同。code
isEqualToStringorm
(BOOL)isEqualToString:(NSString *)aString 是NSString类的实例方法,它主要用于比较两个字符串中的内容是否相同,而非比较两个字符串所在内存地址。该方法经常使用。对象
自定义类的isEqualip
在开发需求中,若是有比较两个类对象是否具备等同性时,一般会根据需求来对父类的isEqual进行改写,这里的作法一般是:内存
先建立自定义的等同性判断方法,代码以下:开发
-(BOOL)isEqualToStudent:(Student *)otherStudent{ if(self == otherStudent) return YES; if(![_name isEqualToString:otherStudent.name]) return NO; if(_age != otherStudent.age) return NO; return YES; }
在编写完断定方法后,应改写类中的isEqual实例方法。若是传入的对象为同一个类时,采用刚刚写的自定义方法来断定,不然就交给父类判断:字符串
-(BOOL)isEqual:(id)object{ if([self class] == [object class]) return [self isEqualToStudent:(Student *)object]; else return [super isEqual:object]; }
Hash主要是用于NSMutableSet中的判断添加的新对象是否已经存在了容器当中,若是不存在,则将新对象放入Set容器中,而判断的依据就是根据实例对象的Hash属性。string
可是由于不一样对象的Hash值会有冲突的可能性(相同的对象Hash值必定相同,这里的相同指的是内存地址相同),因此最后若是哈希值发生冲突的话,则会调用类中的isEqual方法进行等同性判断。hash
既然都会调用isEqual方法,那么为何不直接按顺序遍历set容器,依次调用isEqual方法?it
答:首先set容器是非顺序容器,它的内存空间结构不是按顺序存储的,若是按插入的顺序遍历,开支很大(固然set的插入是无顺序的)。其次除非是大量数据的存储才会发生冲突的可能,在少许数据的状况下基本上不会出现依次遍历冲突对象的isEqual方法的冲突状况。这样看来以O(1)的时间复杂度就能够完成的任务,固然就选择是它了。
在NSObject对象中,Hash值是根据对象所在的内存空间来进行计算的。因此在修改了isEqual方法后,若是没有修改Hash的setter方法,系统在执行set容器操做该对象时,仍然会之内存地址为准来判断两个对象是否相同。因此为了防止出现这种状况,在修改等同性判断方法的时候应顺便修改Hash的setter方法
自定义Hash方法
当自定义完isEqual后,通常为了防止后面的开发用到有关set容器出现意外,因此通常都会对Hash的Setter方法进行修改。代码以下:
-(NSUInteger)hash{ //这里的字符串只要能体现出类对象的name和age便可(依需求而定) NSString* stringToHash=[NSString stringWithFormat:@"%@ %i",_name,_age]; return [stringToHash hash]; }
但这里有个弊端,由于在这里新建一个字符串的开销很大(与返回一个属性值相比较)。因此通常采起下面这种策略:
-(NSUInteger)hash{ NSUInteger nameHash=[_name hash]; NSUInteger ageHash=_age; return nameHash^ageHash; }
这种方法既考虑了开销也考虑了属性的相关性和随机性。---------Effective OC
Hash的调用顺序
例子以下:
//Student.m -(BOOL)isEqualToStudent:(Student *)otherStudent{ if(self == otherStudent){ NSLog(@"(%@:%i)与(%@:%i)冲突了",_name,_age,otherStudent.name,otherStudent.age); return YES; } if(![_name isEqualToString:otherStudent.name]) return NO; if(_age != otherStudent.age) return NO; NSLog(@"[%@:%i]与[%@:%i]冲突了",_name,_age,otherStudent.name,otherStudent.age); return YES; } -(BOOL)isEqual:(id)object{ if([self class] == [object class]) return [self isEqualToStudent:(Student *)object]; else return [super isEqual:object]; } -(NSUInteger)hash{ return _age; } -(NSString *)description{ return [NSString stringWithFormat:@"%@:%i",_name,_age ]; }
//main.m Student* stu1 = [[Student alloc] initWithName:@"小明" age:10]; Student* stu2 = [[Student alloc] initWithName:@"小明" age:10]; Student* stu3 = [[Student alloc] initWithName:@"小明" age:0]; NSMutableSet* set = [NSMutableSet set]; [set addObject:stu1]; //步骤1 [set addObject:stu2]; //步骤2 [set addObject:stu3]; //步骤3 NSLog(@"%@",set);
输出结果:
2020-05-08 22:50:12.172207+0800 effective-OC-test[83370:76838657] [小明:10]与[小明:10]冲突了 2020-05-08 22:50:12.172542+0800 effective-OC-test[83370:76838657] {( 小明:0, 小明:10 )} Program ended with exit code: 0
当执行步骤1时,会先去调用hash方法获得对象的哈希值,由于set容器为空,因此直接根据hash值将对象存入内存当中。stu1成功存入容器中;
当执行步骤2时,一样会先去调用hash方法获得哈希值,由于这里的hash值与age有关,stu1.age==stu2.age,因此两个对象的哈希值相同,这时候就回去调用isEqual方法去判断是否两个对象是等同的。又由于我这里设置的当姓名和年龄一致时,即判断为同一对象,因此这里会打印出冲突。而且不会将stu2存入容器中;
当执行步骤3时,调用完hash方法后,由于age不一样,因此获得的hash值不一样(很小概率会出现相同),从而直接经过的到的hash值去分配内存空间。stu3成功存入容器中。
若是这里将isEqualToStudent的判断条件修改一下,将年龄处修改成:
if(_age == otherStudent.age) return NO;
这时候,再执行一遍,输出以下:
2020-05-08 23:02:13.177534+0800 effective-OC-test[84327:76859194] {( 小明:0, 小明:10, 小明:10 )}
这时候就会发现stu2已经成功存入了容器中,由于在hash值冲突后调用isEqualToStudent方法后,由于两个年龄一致,返回了NO,说明两个对象这时候已经被当成了不一样的两个对象。