相等性 & 本體性 & isEqual && ==

相等性 & 本體性

一爬虱、介紹

首先泥耀,我們要對(duì)于 相等性 和 本體性 進(jìn)行一下區(qū)分。

當(dāng)兩個(gè)物體有一系列相同的可觀測(cè)的屬性時(shí)肴裙,兩個(gè)物體可能是互相 相等 或者 等價(jià) 的。但這兩個(gè)物體本身仍然是 不同的 涌乳,它們各自有自己的 本體蜻懦。 在編程中,一個(gè)對(duì)象的本體和它的內(nèi)存地址是相關(guān)聯(lián)的夕晓。

@implementation NSObject (Approximate)
- (BOOL)isEqual:(id)object {
  return self == object; 
}
@end

isEqual: 表示的是對(duì)象本身相等宛乃,即包含的所有屬性、元素均相等,反之亦然征炼。

二析既、 == & isEqual 的區(qū)別

void checkEqualMethod (void) {
   
    NSString *one = [NSString stringWithFormat:@"1234566"];  // 分配地址
    NSString *two = @"1234566"; // 根據(jù) @"1234566" 分配置地址
    NSString *three = @"1234566"; // 同上,two == three谆奥,值等眼坏,地址等
    
    NSLog(@"- %d", [one isEqual:two]);
    NSLog(@"+ %d", [one isEqualToString:two]);
    NSLog(@"= %d", (one == two));
    NSLog(@"三 %d", (three == two));
}

輸出:
    - 1
    + 1
    = 0
    三 1

通過上面比較可以看出,isEqual是比較對(duì)象的值酸些,當(dāng)對(duì)象是同一字符串類型時(shí)宰译,等價(jià)于isEqualToString,而==擂仍,是地址相等囤屹,才相等。

==適用于字符類型逢渔,不適合用于NSArray肋坚、NSDictionary類型,他來(lái)源于一種稱為字符串駐留的優(yōu)化技術(shù)肃廓,它把一個(gè)不可變字符串對(duì)象的值拷貝給各個(gè)不同的指針智厌。NSString *a 和 *b都指向同樣一個(gè)駐留字符串值 @"Hello"。 注意所有這些針對(duì)的都是靜態(tài)定義的不可變字符串盲赊。

Objective-C 選擇器的名字也是作為駐留字符串儲(chǔ)存在一個(gè)共享的字符串池當(dāng)中的.

themoreyouknow.gif.

三铣鹏、 模擬 NSArray 的實(shí)現(xiàn)

下面是 NSArray 可能使用的解決方案(對(duì)于這個(gè)例子來(lái)說,我們暫時(shí)忽略掉 NSArray 實(shí)際上是一個(gè)類簇哀蘑,真正的實(shí)現(xiàn)會(huì)比這個(gè)復(fù)雜得多)诚卸。

@implementation NSArray (Approximate)

- (BOOL)isEqualToArray:(NSArray *)array {
  // 判空,元素?cái)?shù)相等
  if (!array || [self count] != [array count]) {
    return NO;
  }
  // 遍歷绘迁,判斷對(duì)應(yīng)的元素相等合溺,有一個(gè)不等即不等。
  for (NSUInteger idx = 0; idx < [array count]; idx++) {
      if (![self[idx] isEqual:array[idx]]) {
          return NO;
      }
  }

  return YES;
}

- (BOOL)isEqual:(id)object {
 
 // 如果對(duì)象本體相等缀台,則相等
  if (self == object) {
    return YES;
  }

  if (![object isKindOfClass:[NSArray class]]) {
    return NO;
  }
  // 如果對(duì)象內(nèi)的所有元素相等棠赛,也算相等
  return [self isEqualToArray:(NSArray *)object];
}
@end

在 Foundation 框架中,下面這些 NSObject 的子類都有自己的相等性檢查實(shí)現(xiàn)膛腐,分別使用下面這些方法:

對(duì)應(yīng)該相等性方法
NSData isEqualToData:
NSDate isEqualToDate:
NSDictionary isEqualToDictionary:
NSHashTable isEqualToHashTable:
NSIndexSet isEqualToIndexSet:
NSNumber isEqualToNumber:
NSOrderedSet isEqualToOrderedSet:
NSSet isEqualToSet:
NSString isEqualToString:
NSTimeZone isEqualToTimeZone:
NSValue isEqualToValue:
NSAttributedString isEqualToAttributedString

對(duì)上面這些類來(lái)說睛约,當(dāng)需要對(duì)它們的兩個(gè)實(shí)例進(jìn)行比較時(shí),推薦使用這些高層方法而不是直接使用 isEqual:哲身。

四辩涝、 散列

對(duì)于面向?qū)ο缶幊虂?lái)說,對(duì)象相等性檢查的主要用例勘天,就是確定一個(gè)對(duì)象是不是一個(gè)集合的成員怔揩。為了加快這個(gè)過程棍丐,子類當(dāng)中需要實(shí)現(xiàn) hash 方法:

對(duì)象相等具有 交換性

([a isEqual:b] ? [b isEqual:a])

如果兩個(gè)對(duì)象相等,它們的 hash 值也一定是相等的

([a isEqual:b] ? [a hash] == [b hash])

反過來(lái)則不然沧踏,兩個(gè)對(duì)象的散列值相等不一定意味著它們就是相等的

([a hash] == [b hash] ?? [a isEqual:b])


在子類中實(shí)現(xiàn) -isEqual: 和 hash

下面是一個(gè)在子類中重載默認(rèn)相等性檢查時(shí)可能的實(shí)現(xiàn):

@interface Person
@property NSString *name;
@property NSDate *birthday;

- (BOOL)isEqualToPerson:(Person *)person;
@end

@implementation Person

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

#pragma mark - NSObject

- (BOOL)isEqual:(id)object {
  if (self == object) {
    return YES;
  }

  if (![object isKindOfClass:[Person class]]) {
    return NO;
  }

  return [self isEqualToPerson:(Person *)object];
}

- (NSUInteger)hash {
  return [self.name hash] ^ [self.birthday hash];
}

五、Swfit--Equatable

Equatable 類型的值可以用于判定是否相等巾钉。聲明一個(gè) Equatable 類型有很多好處翘狱,尤其是需要比較的值被放進(jìn)了一個(gè) Array 的時(shí)候。

func ==(lhs: Self, rhs: Self) -> Bool

對(duì)于帶有多類型的相等砰苍,是根據(jù)每個(gè)類型的元素是否相等來(lái)判定的潦匈。例如有一個(gè) Complex 類型,它帶有一個(gè)遵從 SignedNumeric 類型的 T 類型:

使用 SignedNumeric 作為基本數(shù)字類型便捷操作方法赚导,它繼承于 Comparable(也是一種 Equatable茬缩,下面的章節(jié)會(huì)提到)和 IntegerLiteralConvertible。Int吼旧、Double 和 Float 都遵從這個(gè)規(guī)則凰锡。

  • 實(shí)現(xiàn)一個(gè)Complex類型使用==判斷兩個(gè)同類型的對(duì)變量是否相等。

    struct Complex<T: SignedNumeric> {
        let real: T
        let imaginary: T
    }
    
    //因?yàn)?復(fù)數(shù)(complex number) 由實(shí)部和虛部組成圈暗,當(dāng)且僅當(dāng)兩個(gè)復(fù)數(shù)的兩部分均相等時(shí)才能說這兩個(gè)復(fù)數(shù)相等:
    extension Complex: Equatable {}
    
    // MARK: Equatable
    // 使當(dāng)前類支持 == 判等
    func ==<T>(lhs: Complex<T>, rhs: Complex<T>) -> Bool {
        return lhs.real == rhs.real && lhs.imaginary == rhs.imaginary
    }
    

    使用

    let a = Complex<Double>(real: 1.0, imaginary: 2.0)
    let b = Complex<Double>(real: 1.0, imaginary: 2.0)
    a == b // true
    a != b // false
    
  • 引用類型實(shí)現(xiàn) ==判斷相等

    對(duì)于 Swift 中的引用類型掂为,可以根據(jù) ObjectIdentifier 構(gòu)建對(duì)象來(lái)判斷兩個(gè)對(duì)象是否相等:

    class Object: Equatable {}
        
    // MARK: Equatable
    func ==(lhs: Object, rhs: Object) -> Bool {
        
        return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
    }
    Object() == Object() // false
    

    下面例子,通過ObjectIdentifier實(shí)現(xiàn)判斷相個(gè)對(duì)象是否相等员串,注意是完全相等勇哗,即地址、值皆等寸齐。

    class BLFather: NSObject {
    
        var name: String? = ""
        var age: Int = 0
    
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
    }
    
    extension BLFather {
    
        static func ==(lhs: BLFather, rhs: BLFather) -> Bool {
        
            return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
        }
    }
    
    let littleF = BLFather(name: "小叔", age: 40)
    let middleF = BLFather(name: "爸", age: 45)
    let largeF = BLFather(name: "爸", age: 45)
    
    print("-------\(littleF == littleF)")// true
    print("=======\(middleF == largeF)") // false
    
    

    下面例子欲诺,通過Equatable協(xié)議,實(shí)現(xiàn)判斷相個(gè)對(duì)象是否相等渺鹦,注意是值相等扰法,地址不一定相等。

    class BLPerson {
        var name: String? = ""
        var age: Int = 0
    
        init(name: String, age: Int) {
            self.name = name
            self.age = age
      }
    }
    
    extension BLPerson: Equatable {
    
         static func == (lhs: BLPerson, rhs: BLPerson) -> Bool {
    
        return lhs.name == rhs.name && lhs.age == rhs.age
      }
    }
    
    let man = BLPerson(name: "男人", age: 20)
    let woman = BLPerson(name: "女人", age: 18)
    let other = BLPerson(name: "女人", age: 18)
    
    print(man == woman)// false
    print(other == woman)// true
    
    

六海铆、Swift--Comparable

在 Equatable 基礎(chǔ)上建立的 Comparable 提供了更具體的不等條件迹恐,能夠判斷左邊的值是比右邊大還是比右邊小。

遵循 Comparable 協(xié)議的類型應(yīng)該實(shí)現(xiàn)以下幾種操作符:

  • func <=(lhs: Self, rhs: Self) -> Bool
  • func >(lhs: Self, rhs: Self) -> Bool
  • func >=(lhs: Self, rhs: Self) -> Bool

我們發(fā)現(xiàn) == 不見了卧斟,因?yàn)?>= 是 > 和 == 的組合殴边。因?yàn)?Comparable 繼承自 Equatable,所以它也應(yīng)該提供 == 方法珍语。

這也是理解其本質(zhì)的關(guān)鍵點(diǎn):< 也不見了锤岸。“小于” 方法去哪了板乙?其實(shí)它在 Comparable 協(xié)議中是偷。為什么知道這一點(diǎn)很重要呢拳氢?像我們?cè)?the article about Swift Default Protocol Implementations 中提到的,Swift 標(biāo)準(zhǔn)庫(kù)提供了完全基于 Comparable 的 Comparable 協(xié)議蛋铆。這個(gè)設(shè)計(jì)簡(jiǎn)直完美馋评。因?yàn)樗械谋容^方法都可以僅由 < 和 == 推論出,這就讓實(shí)用性大大增加了刺啦。

更復(fù)雜的樣例可以見 CSSSelector 結(jié)構(gòu)留特,它實(shí)現(xiàn)了 selector 的 cascade ordering:

import Foundation

struct CSSSelector {
    let selector: String

    struct Specificity {
        let id: Int
        let `class`: Int
        let element: Int

        init(_ components: [String]) {
            var (id, `class`, element) = (0, 0, 0)
            for token in components {
                if token.hasPrefix("#") {
                    id++
                } else if token.hasPrefix(".") {
                    `class`++
                } else {
                    element++
                }
            }

            self.id = id
            self.`class` = `class`
            self.element = element
        }
    }

    let specificity: Specificity

    init(_ string: String) {
        self.selector = string

        // Na?ve tokenization, ignoring operators, pseudo-selectors, and `style=`.
        let components: [String] = self.selector.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
        self.specificity = Specificity(components)
    }
}

我們知道 CSS Selector 是通過評(píng)分和順序來(lái)判斷相等的,兩個(gè) selector 當(dāng)且僅當(dāng)它們的評(píng)分和順序都相同時(shí)才指向相同元素:

extension CSSSelector: Equatable {}

// MARK: Equatable

func ==(lhs: CSSSelector, rhs: CSSSelector) -> Bool {
    // Na?ve equality that uses string comparison rather than resolving equivalent selectors
    return lhs.selector == rhs.selector
}

拋開這種方法玛瘸,selector 是通過 specificity 來(lái)確定相等性的:

extension CSSSelector.Specificity: Comparable {}

// MARK: Comparable

func <(lhs: CSSSelector.Specificity, rhs: CSSSelector.Specificity) -> Bool {
    return lhs.id < rhs.id ||
        lhs.`class` < rhs.`class` ||
        lhs.element < rhs.element
}

// MARK: Equatable

func ==(lhs: CSSSelector.Specificity, rhs: CSSSelector.Specificity) -> Bool {
    return lhs.id == rhs.id &&
           lhs.`class` == rhs.`class` &&
           lhs.element == rhs.element
}

把這些都結(jié)合在一起:

為了理解的更為清楚蜕青,我們這里認(rèn)為 CSSSelector 遵從 StringLiteralConvertible 協(xié)議.

let a: CSSSelector = "#logo"
let b: CSSSelector = "html body #logo"
let c: CSSSelector = "body div #logo"
let d: CSSSelector = ".container #logo"

b == c // false
b.specificity == c.specificity // true
c.specificity < a.specificity // false
d.specificity > c.specificity // true

七、Swift--Hashable

另一個(gè)重要的協(xié)議是從 Equatable 演變而來(lái)的 Hashable糊渊。

只有 Hashable 類型可以被存儲(chǔ)在 Swift 的 Dictionary 中:

struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConvertible { ... }

一個(gè)遵從 Hashable 協(xié)議的類型必須提供 hashValue 屬性的 getter右核。

protocol Hashable : Equatable {
    /// Returns the hash value.  The hash value is not guaranteed to be stable
    /// across different invocations of the same program.  Do not persist the hash
    /// value across program runs.
    ///
    /// The value of `hashValue` property must be consistent with the equality
    /// comparison: if two values compare equal, they must have equal hash
    /// values.
    var hashValue: Int { get }
}

下面這些 Swift 內(nèi)建類型都實(shí)現(xiàn)了 hashValue:

  • Double
  • Float, Float80
  • Int, Int8, Int16, Int32, Int64
  • UInt, UInt8, UInt16, UInt32, UInt64
  • String
  • UnicodeScalar
  • ObjectIdentifier

學(xué)習(xí)原文 https://nshipster.cn/equality/.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市渺绒,隨后出現(xiàn)的幾起案子贺喝,更是在濱河造成了極大的恐慌,老刑警劉巖宗兼,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搜变,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡针炉,警方通過查閱死者的電腦和手機(jī)挠他,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)篡帕,“玉大人殖侵,你說我怎么就攤上這事×眨” “怎么了拢军?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)怔鳖。 經(jīng)常有香客問我茉唉,道長(zhǎng),這世上最難降的妖魔是什么结执? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任度陆,我火速辦了婚禮,結(jié)果婚禮上献幔,老公的妹妹穿的比我還像新娘懂傀。我一直安慰自己,他們只是感情好蜡感,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布蹬蚁。 她就那樣靜靜地躺著恃泪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪犀斋。 梳的紋絲不亂的頭發(fā)上贝乎,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音叽粹,去河邊找鬼糕非。 笑死,一個(gè)胖子當(dāng)著我的面吹牛球榆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播禁筏,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼持钉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了篱昔?” 一聲冷哼從身側(cè)響起每强,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎州刽,沒想到半個(gè)月后空执,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡穗椅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年辨绊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匹表。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡门坷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出袍镀,到底是詐尸還是另有隱情默蚌,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布苇羡,位于F島的核電站绸吸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏设江。R本人自食惡果不足惜锦茁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叉存。 院中可真熱鬧蜻势,春花似錦、人聲如沸鹉胖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至挠铲,卻和暖如春冕屯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拂苹。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工安聘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓢棒。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓浴韭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親脯宿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子念颈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359