NSString是怎樣比較字符串相等的

版權(quán)聲明:本文源自簡書【九昍】历等,歡迎轉(zhuǎn)載胸完,轉(zhuǎn)載請務(wù)必注明出處: http://www.reibang.com/p/abb3b69172b9

在平時(shí)開發(fā)的時(shí)候經(jīng)常用到[NSString isEqualToString]這個(gè)方法蠕趁,但是卻不清楚它具體是怎么實(shí)現(xiàn)的铜涉,所以決定看下系統(tǒng)源碼早直,了解一下它的內(nèi)部實(shí)現(xiàn)做院。

根據(jù)蘋果官方Guide:【Concepts in Objective-C Programming: Introspection 】
可以得知如果要重寫isEqual: 則需要同時(shí)重寫hash嘹裂。這讓我想當(dāng)然的以為isEqual內(nèi)部會(huì)使用hash做一次判斷眠寿,然后再進(jìn)行字符串比較,不過事實(shí)并非如此焦蘑。

通過翻看static Boolean __CFStringEqual(CFTypeRef cf1, CFTypeRef cf2) 的源碼盯拱,可以得到下面的信息

static Boolean __CFStringEqual(CFTypeRef cf1, CFTypeRef cf2) {
    CFStringRef str1 = (CFStringRef)cf1;
    CFStringRef str2 = (CFStringRef)cf2;
    const uint8_t *contents1;
    const uint8_t *contents2;
    CFIndex len1;

    contents1 = (uint8_t *)__CFStrContents(str1);
    contents2 = (uint8_t *)__CFStrContents(str2);
    len1 = __CFStrLength2(str1, contents1);

    //****X 首先判斷長度是否相等
    if (len1 != __CFStrLength2(str2, contents2)) return false;

    contents1 += __CFStrSkipAnyLengthByte(str1);
    contents2 += __CFStrSkipAnyLengthByte(str2);

    //****X 根據(jù)兩個(gè)字符串是否為Unicode編碼分別做判斷,判斷方式是逐個(gè)取字符做對比
    if (__CFStrIsEightBit(str1) && __CFStrIsEightBit(str2)) { // 都不是Unicode
        return memcmp((const char *)contents1, (const char *)contents2, len1) ? false : true;
    } else if (__CFStrIsEightBit(str1)) {   /* One string has Unicode contents */
        CFStringInlineBuffer buf;
        CFIndex buf_idx = 0;

        CFStringInitInlineBuffer(str1, &buf, CFRangeMake(0, len1));
        for (buf_idx = 0; buf_idx < len1; buf_idx++) {
            if (__CFStringGetCharacterFromInlineBufferQuick(&buf, buf_idx) != ((UniChar *)contents2)[buf_idx]) return false;
        }
    } else if (__CFStrIsEightBit(str2)) {   /* One string has Unicode contents */
        CFStringInlineBuffer buf;
        CFIndex buf_idx = 0;

        CFStringInitInlineBuffer(str2, &buf, CFRangeMake(0, len1));
        for (buf_idx = 0; buf_idx < len1; buf_idx++) {
            if (__CFStringGetCharacterFromInlineBufferQuick(&buf, buf_idx) != ((UniChar *)contents1)[buf_idx]) return false;
        }
    } else {                    /* Both strings have Unicode contents */
        CFIndex idx;
        for (idx = 0; idx < len1; idx++) {
            if (((UniChar *)contents1)[idx] != ((UniChar *)contents2)[idx]) return false;
        }
    }
    return true;
}
    

既然hash方法不是用來提高isEqual:方法的效率的例嘱,那它的作用是什么狡逢?
來看一下isEqual:和hash方法的實(shí)現(xiàn):

[NSObject hash]的實(shí)現(xiàn)

uintptr_t _objc_rootHash(id obj)
{
    return (uintptr_t)obj;
}

+ (NSUInteger)hash {
    return _objc_rootHash(self);
}

[NSObject isEqual:]的實(shí)現(xiàn)

- (BOOL)isEqual:(id)obj {
    return obj == self;
}

可以看出,對于NSObject無論是hash還是isEqual方法拼卵,都是用對象地址作為依據(jù)奢浑,所以對于NSObject,如果hash值相同isEqual:的返回值就是YES腋腮。

如果我們需要實(shí)現(xiàn)自定義的isEqual該怎么做雀彼,首先看下apple官方demo的實(shí)現(xiàn)

- (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;
}

官方的推薦是實(shí)現(xiàn)一個(gè)isEqualToType:方法,然后在isEqual內(nèi)部調(diào)用isEqualToType方法即寡,并且在isEqual內(nèi)部檢查對象類型及合法性徊哑。

當(dāng)修改完isEqual以后,如果不修改hash方法聪富,那么此時(shí)若有兩個(gè)不同的MyWidget對象莺丑,并且他們的name、data相同,此時(shí)他們的hash仍然是取的自身地址梢莽,此時(shí)hash不相等萧豆,不滿足對象相等hash一定相等的原則。

@implementation Model

- (id)copyWithZone:(nullable NSZone *)zone {
    
    Model *result = [[[self class] allocWithZone:zone] init];
    
    result.firstName = self.firstName;
    result.lastName  = self.lastName;
    
    return result;
}

- (NSUInteger)hash {
    NSLog(@"%@ call %s", self, __func__);
    return NSUINTROTATE([_firstName hash], NSUINT_BIT / 2) ^ [_lastName hash];
}

- (BOOL)isEqual:(id)other {
    NSLog(@"%@ call %s", self, __func__);
    if (other == self)
        return YES;
    if (!other || ![other isKindOfClass:[self class]])
        return NO;
    return [self isEqualToModel:other];
}

- (BOOL)isEqualToModel:(Model *)other {
    if (self == other)
        return YES;
    if (![(id)[self firstName] isEqual:[other firstName]])
        return NO;
    if (![[self lastName] isEqual:[other lastName]])
        return NO;
    return YES;
}
@end

-----------------------------------------------------------------
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Model *model1 = [[Model alloc] init];
    model1.firstName = @"A";
    model1.lastName = @"B";
    Model *model2 = [[Model alloc] init];
    model2.firstName = @"A";
    model2.lastName = @"B";
    
    NSMutableDictionary *mutDict = [NSMutableDictionary dictionary];
    
    [mutDict setObject:@"" forKey:model1];
    
    NSLog(@"**********************************");
    [mutDict setObject:@"" forKey:model2];
程序運(yùn)行后打印的log如下:

2016-12-19 16:03:27.236 test[28108:2027961] <Model: 0x618000024bc0> call -[Model hash]
2016-12-19 16:03:27.236 test[28108:2027961] <Model: 0x610000024860> call -[Model hash]
2016-12-19 16:03:27.236 test[28108:2027961] **********************************
2016-12-19 16:03:27.237 test[28108:2027961] <Model: 0x618000024c60> call -[Model hash]
2016-12-19 16:03:27.237 test[28108:2027961] <Model: 0x610000024860> call -[Model isEqual:]

首先解釋下為什么第一次setObject:forKey:調(diào)用了兩次hash方法第一次調(diào)用hash方法是為了獲取hash值從而做進(jìn)一步判斷昏名,這是因?yàn)槲覀儧]有指定字典的Capacity涮雷,這種情況下Capacity的值為0,這時(shí)候我們向字典里添加元素轻局,字典空間不夠用洪鸭,會(huì)重新申請空間,此時(shí)需要在新申請的控件通過hash確定對象的地址嗽交,所以第二次調(diào)用hash這個(gè)方法卿嘲。

從上面的log可以看到在以hash表為基礎(chǔ)的對象(NSDictionary、NSSet等)中存儲(chǔ)數(shù)據(jù)時(shí)夫壁,若hash相等則會(huì)調(diào)用對象的isEqual方法進(jìn)一步判斷對象是否相等拾枣,從而確定對象是否已經(jīng)存在,如果我們只修改isEqual方法盒让,則可能會(huì)出現(xiàn)兩個(gè)isEqual的對象由于hash值不相等導(dǎo)致被誤判為兩個(gè)不相等對象的情況梅肤,這就是為什么必須要同時(shí)修改isEqual:和hash方法最重要的原因。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邑茄,一起剝皮案震驚了整個(gè)濱河市姨蝴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肺缕,老刑警劉巖左医,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異同木,居然都是意外死亡浮梢,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門彤路,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秕硝,“玉大人,你說我怎么就攤上這事洲尊≡恫颍” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵坞嘀,是天一觀的道長躯护。 經(jīng)常有香客問我,道長姆吭,這世上最難降的妖魔是什么榛做? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮内狸,結(jié)果婚禮上检眯,老公的妹妹穿的比我還像新娘。我一直安慰自己昆淡,他們只是感情好锰瘸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昂灵,像睡著了一般避凝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眨补,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天管削,我揣著相機(jī)與錄音,去河邊找鬼撑螺。 笑死含思,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的甘晤。 我是一名探鬼主播含潘,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼线婚!你這毒婦竟也來了遏弱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤塞弊,失蹤者是張志新(化名)和其女友劉穎漱逸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體游沿,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饰抒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奏候。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片循集。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蔗草,靈堂內(nèi)的尸體忽然破棺而出咒彤,到底是詐尸還是另有隱情,我是刑警寧澤咒精,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布镶柱,位于F島的核電站,受9級特大地震影響模叙,放射性物質(zhì)發(fā)生泄漏歇拆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望故觅。 院中可真熱鬧厂庇,春花似錦、人聲如沸输吏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贯溅。三九已至拄氯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間它浅,已是汗流浹背译柏。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姐霍,地道東北人鄙麦。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像邮弹,于是被迫代替她去往敵國和親黔衡。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容