相等性 & 本体性
一、介绍
首先,我们要对于 相等性 和 本体性 进行一下区分。
当两个物体有一系列相同的可观测的属性时,两个物体可能是互相 相等
或者 等价
的。但这两个物体本身仍然是 不同的
,它们各自有自己的 本体
。 在编程中,一个对象的本体
和它的内存地址是相关联的。
@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