Hash的使用參考了網(wǎng)上的文章 ,對(duì)網(wǎng)上的例子進(jìn)行了驗(yàn)證,同時(shí)將驗(yàn)證結(jié)果與文章進(jìn)行對(duì)比,不一致的地方也做了修改。
iOS系統(tǒng)API給我們提供一個(gè)自動(dòng)過(guò)濾重復(fù)元素的容器 NSMutableSet/NSSet,如:當(dāng)我們向該實(shí)例對(duì)象中添加字符串時(shí),如果重復(fù)添加兩個(gè)相同的字符串,集合中只會(huì)保留一個(gè)。NSMutableSet/NSSet內(nèi)部一些實(shí)現(xiàn)機(jī)制要比我們自己寫的濾重方法效率高。但是對(duì)于自定義一個(gè)類如Person,如果想利用NSMutableSet/NSSet來(lái)過(guò)濾重復(fù)元素(如多個(gè)Person實(shí)例的uid相同),我們必須要同時(shí)實(shí)現(xiàn)- (BOOL)isEqual:和- (NSUInteger)hash這兩個(gè)方法。這里先簡(jiǎn)單介紹他們的關(guān)系:兩個(gè)相等的實(shí)例,他們的hash值一定相等。但是hash值相等的兩個(gè)實(shí)例,不一定相等。重點(diǎn)來(lái)了,利用 NSMutableSet/NSSet 具體如何實(shí)現(xiàn)過(guò)濾 Person 重復(fù)元素 ?
關(guān)于- (BOOL)isEqual:方法
- 為什么要有isEqual方法?
OC 中 == 運(yùn)算符判斷的是指針是否相等, 而 isEqual 方法內(nèi)部除了判斷指針是否相等,還要判斷對(duì)象的屬性是否相等。
首先貼個(gè)蘋果官方重寫isEqual 的demo
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToWidget:other];
}
- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
if (self == aWidget)
return YES;
if (![(id)[self name] isEqual:[aWidget name]])
return NO;
if (![[self data] isEqualToData:[aWidget data]])
return NO;
return YES;
}
簡(jiǎn)單說(shuō)一下:
首先都會(huì)判斷 指針是否相等 ,相等直接返回YES,
不相等再判斷是否是同類對(duì)象或非空,空或非同類對(duì)象直接返回NO,
而后依次判斷對(duì)象對(duì)應(yīng)的屬性是否相等,若均相等,返回YES
- 如何重寫isEqual方法?
但對(duì)于自定義類型來(lái)說(shuō), 做判等時(shí)通常需要重寫isEqual方法。
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;
@end
if (self == object) {
return YES;
}
if (![object isKindOfClass:[Person class]]) {
return NO;
}
return [self isEqualToPerson:(Person *)object];
}
- (BOOL)isEqualToPerson:(Person *)person {
if (!person) {
return NO;
}
BOOL haveEqualNames = (!self.name && !person.name) || [self.name isEqualToString:person.name];
BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];
return haveEqualNames && haveEqualBirthdays;
}
上述代碼主要步驟如下:
1、 ==運(yùn)算符判斷指針是否相同, 如果相同,那么對(duì)象也相同
2、 判斷是否是同一類型, 這樣不僅可以提高判等的效率, 還可以避免隱式類型轉(zhuǎn)換帶來(lái)的潛在風(fēng)險(xiǎn)
3、通過(guò)封裝的isEqualToPerson方法, 提高代碼復(fù)用性
4、 判斷person是否是nil, 做參數(shù)有效性檢查
5、 對(duì)各個(gè)屬性分別使用默認(rèn)判等方法進(jìn)行判斷
6、 返回所有屬性判等的與結(jié)果
關(guān)于- (NSUInteger)hash方法
- hash方法什么時(shí)候被調(diào)用?
如果在 Person 類中重寫- (NSUInteger)hash方法,該方法只在 Person 實(shí)例對(duì)象被添加至NSSet或?qū)erson實(shí)例對(duì)象設(shè)置為NSDictionary的key 時(shí)會(huì)調(diào)用。注意是設(shè)置為 key 而不是 value
- hash方法和判等的關(guān)系?
為了優(yōu)化判等的效率, 基于 hash 的 NSSet 和 NSDictionary 在判斷成員是否相等時(shí), 通常會(huì)這樣做:
首先判斷 hash 值是否和目標(biāo) hash 值相等。如果相同再進(jìn)行對(duì)象之后的判等邏輯, 作為判等的結(jié)果; 如果不等, 直接判斷為不相等。
- 如何重寫 hash 方法?
很多人在iOS開(kāi)發(fā)中, 都是這么重寫hash方法的,如果自己親自測(cè)試一下會(huì)發(fā)現(xiàn)直接重寫父類方法并不能實(shí)現(xiàn)過(guò)濾重復(fù)元素的功能。
- (NSUInteger)hash {
return [super hash];
}
對(duì)于上面的 Person 類正確的 Hash 實(shí)現(xiàn)方法應(yīng)該是借助位運(yùn)算。代碼如下:
- (NSUInteger)hash {
return [self.name hash] ^ [self.birthday hash];
}
實(shí)現(xiàn)- (BOOL)isEqual: 和 - (NSUInteger)hash方法,實(shí)現(xiàn)過(guò)濾自定義實(shí)例的功能
person.h
@interface Person : NSObject
@property (nonatomic, assign) NSInteger uid;
@property (nonatomic, strong) NSString *name;
- (instancetype)initWithID:(NSInteger)uid name:(NSString *)name;
@end
person.m
#import "Person.h"
@implementation Person
- (instancetype)initWithID:(NSInteger)uid name:(NSString *)name{
if (self = [super init]) {
self.uid = uid;
self.name = name;
}
return self;
}
- (BOOL)isEqual:(Person *)object{
BOOL result;
if (self == object) {
result = YES;
}else{
if (object.uid == self.uid) {
result = YES;
}else{
result = NO;
}
}
NSLog(@"%@ compare with %@ result = %@",self,object,result ? @"Equal":@"NO Equal");
return result;
}
- (NSUInteger)hash{
NSUInteger hashValue = self.uid;
//在這里只需要比較uid就行。這樣的話就滿足如果兩個(gè)實(shí)例相等,那么他們的 hash 一定相等,但反過(guò)來(lái)hash值相等,那么兩個(gè)實(shí)例不一定相等。但是在 Person 這個(gè)實(shí)例中,hash值相等那么實(shí)例一定相等。(不考慮繼承之類的)
NSLog(@"hash = %lu,addressValue = %lu,address = %p",(NSUInteger)hashValue,(NSUInteger)self,self);
// 如果返回的 值 與 之前返回的值不一樣 則不會(huì)繼續(xù)判斷 isEqual 方法
return hashValue;
}
- (NSString *)description{
return [NSString stringWithFormat:@"%p(%ld,%@)",self,self.uid,self.name];
}
@end
調(diào)用的地方
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableSet *mutSet = [NSMutableSet set];
Person *person1 = [[Person alloc] initWithID:1 name:@"nihao"];
NSLog(@"begin add %@",person1);
[mutSet addObject:person1];
person1.name = @"nihaoma";
[mutSet addObject:person1];
Person *person2 = [[Person alloc] initWithID:1 name:@"wohao"];
NSLog(@"begin add %@",person2);
[mutSet addObject:person2];
// count 上述的兩次操作 只會(huì)保留第一次操作結(jié)果
NSLog(@"count = %lu",(unsigned long)mutSet.count);
Person *person3 = [[Person alloc] initWithID:2 name:@"tahao"];
NSLog(@"begin add %@",person3);
[mutSet addObject:person3];
// count
NSLog(@"count = %lu",(unsigned long)mutSet.count);
// Do any additional setup after loading the view, typically from a nib.
}
注解:
NSMutableSet/NSSet中添加 Person 對(duì)象的時(shí)候,就會(huì)調(diào)用- (NSUInteger)hash方法。
第一次添加person1 的時(shí)候 只會(huì)調(diào)用 - (NSUInteger)hash 方法 ,判等的方法不會(huì)走。
NSMutableSet/NSSet中添加
person2對(duì)象的時(shí)候,如果NSMutableSet/NSSet 中之前就已經(jīng)存在 person1對(duì)象,且 person1 對(duì)象的 - (NSUInteger)hash返回值和person2的- (NSUInteger)hash返回值相等, 則 會(huì)繼續(xù)調(diào)用- (BOOL)isEqual:方法 ,其中此方法以person2為參數(shù);否則不等, 繼續(xù)下一個(gè)元素判斷。如果再次添加person3對(duì)象,hash值也是一樣的話,那么就會(huì)在上述步驟的基礎(chǔ)上,繼續(xù)調(diào)用判等方法。