相等性 & 本体性 & isEqual && ==

相等性 & 本体性

一、介绍

首先,我们要对于 相等性 和 本体性 进行一下区分。

当两个物体有一系列相同的可观测的属性时,两个物体可能是互相 相等 或者 等价 的。但这两个物体本身仍然是 不同的 ,它们各自有自己的 本体。 在编程中,一个对象的本体和它的内存地址是相关联的。

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

isEqual: 表示的是对象本身相等,即包含的所有属性、元素均相等,反之亦然。

二、 == & isEqual 的区别

void checkEqualMethod (void) {
   
    NSString *one = [NSString stringWithFormat:@"1234566"];  // 分配地址
    NSString *two = @"1234566"; // 根据 @"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是比较对象的值,当对象是同一字符串类型时,等价于isEqualToString,而==,是地址相等,才相等。

==适用于字符类型,不适合用于NSArray、NSDictionary类型,他来源于一种称为字符串驻留的优化技术,它把一个不可变字符串对象的值拷贝给各个不同的指针。NSString *a 和 *b都指向同样一个驻留字符串值 @"Hello"。 注意所有这些针对的都是静态定义的不可变字符串。

Objective-C 选择器的名字也是作为驻留字符串储存在一个共享的字符串池当中的.

themoreyouknow.gif.

三、 模拟 NSArray 的实现

下面是 NSArray 可能使用的解决方案(对于这个例子来说,我们暂时忽略掉 NSArray 实际上是一个类簇,真正的实现会比这个复杂得多)。

@implementation NSArray (Approximate)

- (BOOL)isEqualToArray:(NSArray *)array {
  // 判空,元素数相等
  if (!array || [self count] != [array count]) {
    return NO;
  }
  // 遍历,判断对应的元素相等,有一个不等即不等。
  for (NSUInteger idx = 0; idx < [array count]; idx++) {
      if (![self[idx] isEqual:array[idx]]) {
          return NO;
      }
  }

  return YES;
}

- (BOOL)isEqual:(id)object {
 
 // 如果对象本体相等,则相等
  if (self == object) {
    return YES;
  }

  if (![object isKindOfClass:[NSArray class]]) {
    return NO;
  }
  // 如果对象内的所有元素相等,也算相等
  return [self isEqualToArray:(NSArray *)object];
}
@end

在 Foundation 框架中,下面这些 NSObject 的子类都有自己的相等性检查实现,分别使用下面这些方法:

对应该相等性方法
NSData isEqualToData:
NSDate isEqualToDate:
NSDictionary isEqualToDictionary:
NSHashTable isEqualToHashTable:
NSIndexSet isEqualToIndexSet:
NSNumber isEqualToNumber:
NSOrderedSet isEqualToOrderedSet:
NSSet isEqualToSet:
NSString isEqualToString:
NSTimeZone isEqualToTimeZone:
NSValue isEqualToValue:
NSAttributedString isEqualToAttributedString

对上面这些类来说,当需要对它们的两个实例进行比较时,推荐使用这些高层方法而不是直接使用 isEqual:

四、 散列

对于面向对象编程来说,对象相等性检查的主要用例,就是确定一个对象是不是一个集合的成员。为了加快这个过程,子类当中需要实现 hash 方法:

对象相等具有 交换性

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

如果两个对象相等,它们的 hash 值也一定是相等的

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

反过来则不然,两个对象的散列值相等不一定意味着它们就是相等的

([a hash] == [b hash] ¬⇒ [a isEqual:b])


在子类中实现 -isEqual: 和 hash

下面是一个在子类中重载默认相等性检查时可能的实现:

@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 类型的值可以用于判定是否相等。声明一个 Equatable 类型有很多好处,尤其是需要比较的值被放进了一个 Array 的时候。

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

对于带有多类型的相等,是根据每个类型的元素是否相等来判定的。例如有一个 Complex 类型,它带有一个遵从 SignedNumeric 类型的 T 类型:

使用 SignedNumeric 作为基本数字类型便捷操作方法,它继承于 Comparable(也是一种 Equatable,下面的章节会提到)和 IntegerLiteralConvertible。Int、Double 和 Float 都遵从这个规则。

  • 实现一个Complex类型使用==判断两个同类型的对变量是否相等。

    struct Complex<T: SignedNumeric> {
        let real: T
        let imaginary: T
    }
    
    //因为 复数(complex number) 由实部和虚部组成,当且仅当两个复数的两部分均相等时才能说这两个复数相等:
    extension Complex: Equatable {}
    
    // MARK: Equatable
    // 使当前类支持 == 判等
    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
    
  • 引用类型实现 ==判断相等

    对于 Swift 中的引用类型,可以根据 ObjectIdentifier 构建对象来判断两个对象是否相等:

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

    下面例子,通过ObjectIdentifier实现判断相个对象是否相等,注意是完全相等,即地址、值皆等。

    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协议,实现判断相个对象是否相等,注意是值相等,地址不一定相等。

    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 基础上建立的 Comparable 提供了更具体的不等条件,能够判断左边的值是比右边大还是比右边小。

遵循 Comparable 协议的类型应该实现以下几种操作符:

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

我们发现 == 不见了,因为 >= 是 > 和 == 的组合。因为 Comparable 继承自 Equatable,所以它也应该提供 == 方法。

这也是理解其本质的关键点:< 也不见了。“小于” 方法去哪了?其实它在 Comparable 协议中。为什么知道这一点很重要呢?像我们在 the article about Swift Default Protocol Implementations 中提到的,Swift 标准库提供了完全基于 Comparable 的 Comparable 协议。这个设计简直完美。因为所有的比较方法都可以仅由 < 和 == 推论出,这就让实用性大大增加了。

更复杂的样例可以见 CSSSelector 结构,它实现了 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 是通过评分和顺序来判断相等的,两个 selector 当且仅当它们的评分和顺序都相同时才指向相同元素:

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 来确定相等性的:

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
}

把这些都结合在一起:

为了理解的更为清楚,我们这里认为 CSSSelector 遵从 StringLiteralConvertible 协议.

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

另一个重要的协议是从 Equatable 演变而来的 Hashable。

只有 Hashable 类型可以被存储在 Swift 的 Dictionary 中:

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

一个遵从 Hashable 协议的类型必须提供 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 内建类型都实现了 hashValue:

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

学习原文 https://nshipster.cn/equality/.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352