兩個概念 相等性與本體性
來看段程式碼,猜猜結果如何
NSArray *a = @[@1];
NSArray *b = a;
NSArray *c = @[@1];
if (a == b) {
NSLog(@"a == b");
}
if (a == c) {
NSLog(@"a == c");
}
if ([a isEqualToArray:c]){
NSLog(@"a c 具有本體性");
}複製程式碼
列印結果
a == b
a c 具有本體性複製程式碼
這段程式碼,首先我們要搞明白記憶體到底是怎麼分配的。
a,b 都是指標。
[NSObject new]則在堆上分配了一塊記憶體空間
該記憶體空間的首地址被儲存在 a 中。
將a的值 賦值給 b.
如圖:
相等性:當兩個物體有一系列相同的可觀測的屬性時,兩個物體可能是互相相等或者等價的。但這兩個物體仍然是不同的,他們各自有自己的本體。
本體性:在程式設計中,一個物件的本體和它的記憶體地址是相互關聯的。關聯的記憶體地址相同則具有本體性。
三種判等方式
你可能會對以下三種判等方式的實現有疑惑
- isEqual:
- ==
- isEqualToArray:
Objective-C 對於判等的實現
接觸了 相等性 和 本體性之後, 來看看 Objective-C 對於判等是如何實現的.
isEqual:
NSObject 使用 isEqual: 這個方法來測試和其他物件的相等性。在它的基類實現中,相等性檢查本質上就是對本體性的檢查。兩個 NSObject 如果指向了同一個記憶體地址,那它們就被認為是相同的。
子類如果需要重寫isEqual:
- 實現一個新的
isEqualTo__ClassName__
方法,進行實際意義上的值的比較。 - 重寫 isEqual: 方法進行類和物件的本體性檢查,如果失敗則回退到上面提到的值比較方法。
- 重寫 hash 方法
==
是對本體性的比較,比較指標。
isEqualToArray:
是對相等性進行檢查。
NSArray 作為NSObject的子類,實現了 isEqualToArray:的方法。
isEqualToArray:對 相等性進行檢查。
相等性檢查的實現
在程式設計中,是如何實現相等性檢查的呢?
藉助Hash來實現。
關於Hash的知識,Wiki_Hash
如果兩個物件相等,它們的 hash 值也一定是相等的(反之不然)
對於自定義的子類來說,判斷相等性需要實現hash方法
給NSHipster糾個錯:
Swift-Comparison-Protocols
中提到的:
Objective-C 中對於物件的比較,== 操作符的運算結果就是來自 isEqual: 方法的結果:
根據文件的意思是有問題的
Each class determines equality for its instances by implementing class-specific comparison logic. The root class, NSObject, measures equality by simple pointer comparison; at the other extreme, the determinants of equality for a custom class might be class membership plus all encapsulated values.
Example:
來自NSHipster
@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];
}複製程式碼
Swift 中的判等
(Swift 並不是我的主力語言 簡單介紹下)
Swift === 判讀本體性
Swift == 判斷相等性
Objective-C == 判斷兩個物件是否是同一個引用
你可以通過重寫isEqual:方法來改變==的實現。
如果沒有重寫,預設呼叫的NSObject的實現。
或者更進一步去實現類似 -isEqualToString: 這樣的 -isEqualToClass: 的帶有型別資訊的方法來進行內容判等。
在 Swift 中情況大不一樣,Swift 裡的 == 是一個操作符的宣告,在 Equatable 裡宣告瞭這個操作符的介面方法:
protocol Equatable {
func ==(lhs: Self, rhs: Self) -> Bool
}複製程式碼
實現這個介面的型別需要定義適合自己型別的==操作符,如果我們認為兩個輸入有相等關係的話,就應該返回true.
Swift 中於 OC == 相對應地是 === 操作符
一些特殊情況
NSString 與 字串駐留
NSString *s1 = @"123";
NSString *s2 = @"123";
NSString *s3 = [NSString stringWithFormat:@"%@",@"123"];
if (s1 == s2) {
NSLog(@"s1 == s2");
}
if (s1 == s3) {
NSLog(@"s1 == s3");
}
if ([s1 isEqualToString:s3]) {
NSLog(@"s1 isEqualToString:s3");
}複製程式碼
列印:
s1 == s2
s1 isEqualToString:s3複製程式碼
由於使用了字串駐留技術
在使用字串字面量來初始化NSString時,字串駐留技術把一個不可變字串物件的值拷貝給各個不同的指標。s1 和 s2都指向同樣一個駐留字串值。
這就導致了 s1 s2 指向相同的記憶體地址。
s1 s3 指向的記憶體地址不同,但是值是相同的。
類族對於判等的影響
首先什麼是類族?簡單介紹下:
Cocoa框架中的類很多都是類族,隱藏了背後的實現。NSNumber,NSArray,還有眾多collection類都是類族。
+ (UIButton *)buttonWithType:(UIButtonType)type;複製程式碼
該方法返回的物件,其型別取決於傳入的按鈕型別。(這就隱藏了實現細節,使用者只需根據需求傳入不同的UIButtonType)
如果我們用物件的型別進行判等自然會出現問題:
id maybeAnArray = /* ...*/;
if ([maybeAnArray class] == [NSArray class]) {
//will never be hit
}複製程式碼
如果不知道類族,你會迷惑這段程式碼的問題到底出在何處。
我們可以改用 isKindOfClass: 方法來進行判斷。
isKindOfClass來確定一個物件是否是一個類的成員,或者是派生自該類的成員
由於類簇中的類 是 抽象類的子類,所以 isKindOfClass: 來判等是可行的。