Hash 在 iOS 中的應(yīng)用

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)用判等方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容