相等性 & 本體性
一爬虱、介紹
首先泥耀,我們要對(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