Objc 相等性判斷
今天做任務(wù)時遇到一個問題讯屈,情況是這樣的:
我新建一個類,然后創(chuàng)建一個這個類的對象,然后將這個類對象放到一個數(shù)組中狐树,在此后使用的時候就從這個數(shù)組中把這個類對象取出來,如果這個數(shù)組中沒有這個類鸿脓,就重新創(chuàng)建一個類抑钟,放入到這個數(shù)組,也就是說這個數(shù)組中始終有一個這個類的對象野哭。但是有一個問題在塔,就是我判斷這個類對象是否在這個數(shù)組中的時候,使用了NSArray的函數(shù)- (BOOL)containsObject:(ObjectType)anObject拨黔,然后問題就出現(xiàn)了蛔溃,這個函數(shù)始終返回NO。用代碼描述:
Person *personA = [[Person alloc] init];
Person *personB = [[Person alloc] init];
NSArray *personArrA = @[personA];
if ([personArrA containsObject:personB]) {
NSLog(@"personArrA 中包含 personB");
} else {
NSLog(@"personArrA 中不包含 personB");
}
如果這樣子判斷的話篱蝇,始終會打印<strong>personArrA 中不包含 personB</strong>
現(xiàn)在就把我的理解記錄一下贺待,出現(xiàn)這個問題的原因是:
NAArray的這個方法- (BOOL)containsObject:(ObjectType)anObject是用來判斷這個數(shù)組的對象元素是否包含指定對象元素,但是這個函數(shù)會去調(diào)用對象元素的- (BOOL)isEqual:(id)object方法去判斷元素對象的相等與否零截,如果這個方法返回為YES則說明這兩個對象元素相等麸塞,即數(shù)組中存在;否則不相等涧衙,即數(shù)組中不存在哪工。如果對象元素沒有實現(xiàn)這個判斷相等的方法,則默認(rèn)調(diào)用基類中的- (BOOL)isEqual:(id)object方法弧哎,而NSObject中這個方法的默認(rèn)實現(xiàn)是比較兩個對象的內(nèi)存地址雁比,而我的Person類沒有實現(xiàn)- (BOOL)isEqual:(id)object方法,所以它會去比較personA和personB的內(nèi)存地址傻铣,這兩個對象的地址肯定不相等章贞,所以這樣子就出現(xiàn)問題了。非洲。
Object中相等性判斷
在Object中鸭限,基類NSObject使用isEqual:這個方法來測試和其他對象的相等性,其實現(xiàn)的本質(zhì)两踏,就是如果兩個對象指向了同一個內(nèi)存地址败京,那么就認(rèn)為是相同的,相等的;可能的實現(xiàn)為:
- (BOOL)isEqual:(id)object {
return self == object;
}
講到這里我們順便說一下<strong>==</strong>這個操作符梦染,這個操作符實際上就是直接比較兩個對象的內(nèi)存地址赡麦,所以在Objc中我們很少使用==這個操作符來判斷兩個對象是否相等朴皆。
如果需要比較兩個對象是否相等,我們應(yīng)該在自己的對象中實現(xiàn)NSObject協(xié)議中聲明的用于判斷等同性的兩個關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
在Foundation框架中泛粹,許多NSObject的子類都有自己的相等性檢查遂铡,如下:
NSAttributedString -isEqualToAttributedString:
NSData -isEqualToData:
NSDate -isEqualToDate:
NSDictionary -isEqualToDictionary:
NSHashTable -isEqualToHashTable:
NSIndexSet -isEqualToIndexSet:
NSNumber -isEqualToNumber:
NSOrderedSet -isEqualToOrderedSet:
NSSet -isEqualToSet:
NSString -isEqualToString:
NSTimeZone -isEqualToTimeZone:
NSValue -isEqualToValue:
所以我們使用框架提供給我們的類對象時直接使用框架提供的等同性判斷方法即可。比如晶姊,NSSting類實現(xiàn)了一個自己獨有的等同性判斷方法扒接,名叫:isEqualToSting:。
NSLog(@"----------字符串相等比較-------------");
NSString *strA = @"ljt";
NSString *strB = [NSString stringWithFormat:@"%@",@"ljt"];
NSLog(@"內(nèi)存地址:strA = %p,strB = %p",strA,strB);
NSLog(@"----------直接==比較---------------");
if (strA == strB) {
NSLog(@"strA == strB");
} else {
NSLog(@"strA != strB");
}
NSLog(@"----------isEqualToString比較---------------");
if ([strA isEqualToString:strB]) {
NSLog(@"strA == strB");
} else {
NSLog(@"strA != strB");
}
日志:
2016-12-07 20:16:06.937 TestString[5852:226853] ----------字符串相等比較-------------
2016-12-07 20:16:06.938 TestString[5852:226853] 內(nèi)存地址:strA = 0x10d50a0a8,strB = 0xa00000000746a6c3
2016-12-07 20:16:06.938 TestString[5852:226853] ----------直接==比較---------------
2016-12-07 20:16:06.938 TestString[5852:226853] strA != strB
2016-12-07 20:16:06.938 TestString[5852:226853] ----------isEqualToString比較---------------
2016-12-07 20:16:06.938 TestString[5852:226853] strA == strB
從代碼中就可以很明想的看出這個方法和==的區(qū)別们衙。
在這里插入一個小插曲
有時候我們會看到使用 == 來判斷字符串是否相等钾怔,比如
NSString *a = @"ljt";
NSString *b = @"ljt";
NSLog(@"內(nèi)存地址:a = %p,b = %p",a,b);
NSLog(@"----------直接==比較---------------");
if (a == b) {
NSLog(@"a == b");
} else {
NSLog(@"a != b");
}
日志:
2016-12-07 20:47:58.731 TestString[5916:236643] 內(nèi)存地址:a = 0x10eb8b088,b = 0x10eb8b088
2016-12-07 20:47:58.732 TestString[5916:236643] ----------直接==比較---------------
2016-12-07 20:47:58.732 TestString[5916:236643] a == b
2016-12-07 20:47:58.732 TestString[5916:236643] ----------isEqualToString比較---------------
2016-12-07 20:47:58.732 TestString[5916:236643] a == b
首先要明確一點,比較NSString對象正確的方法是:-isEqualToString:蒙挑。任何情況下都不要直接使用==來對NSString進行比較宗侦。但是現(xiàn)在看來結(jié)果貌似是正確的,所有這些行為忆蚀,都來源于一種稱為<strong>字符串駐留</strong>的優(yōu)化技術(shù)矾利,它把一個不可變字符串對象的值拷貝給各個不同的指針。NSString *a和*b都指向同樣一個駐留字符串值@"ljt"蜓谋。注意所有這些針對的都是靜態(tài)定義的不可變字符串梦皮。
1、非靜態(tài)定義的字符串呢桃焕?
NSString *str = @"ljt";
NSString *a = [str stringByAppendingString:@"ljt"];
NSString *b = [str stringByAppendingString:@"ljt"];
NSLog(@"內(nèi)存地址:a = %p,b = %p",a,b);
NSLog(@"----------直接==比較---------------");
if (a == b) {
NSLog(@"a == b");
} else {
NSLog(@"a != b");
}
NSLog(@"----------isEqualToString比較---------------");
if ([a isEqualToString:b]) {
NSLog(@"a == b");
} else {
NSLog(@"a != b");
}
日志:
2016-12-07 20:55:27.216 TestString[5957:240029] 內(nèi)存地址:a = 0x7fda58e6f370,b = 0x7fda58e093b0
2016-12-07 20:55:27.217 TestString[5957:240029] ----------直接==比較---------------
2016-12-07 20:55:27.217 TestString[5957:240029] a != b
2016-12-07 20:55:27.217 TestString[5957:240029] ----------isEqualToString比較---------------
2016-12-07 20:55:27.218 TestString[5957:240029] a == b
2剑肯、如果比較的是NSArray和NSDictionary呢?
NSArray *arr1 = @[@"ljt",@"hdu",@"ths"];
NSArray *arr2 = @[@"ljt",@"hdu",@"ths"];
NSLog(@"內(nèi)存地址:arr1 = %p,arr2 = %p",arr1,arr2);
NSLog(@"----------直接==比較---------------");
if (arr1 == arr2) {
NSLog(@"arr1 == arr2");
} else {
NSLog(@"arr1 != arr2");
}
日志:
2016-12-07 20:58:31.704 TestString[5973:241680] 內(nèi)存地址:arr1 = 0x7fff3b523010,arr2 = 0x7fff3b506200
2016-12-07 20:58:31.705 TestString[5973:241680] ----------直接==比較---------------
2016-12-07 20:58:31.705 TestString[5973:241680] arr1 != arr2
綜上:字符串使用使用==比較相等結(jié)果貌似是正確的現(xiàn)象观堂,只是一種特殊情況让网。
自建對象判斷相等性
我們這里創(chuàng)建一個Person的類繼承與NSObject,暫時只實現(xiàn)一個初始方法:
###Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, copy) NSString *age;
- (BOOL)isEqualToPerson:(Person *)person;
@end
###Person.m
#import "Person.h"
@implementation Person
- (instancetype)init {
if (self == [super init]) {
self.name = @"ljt";
self.sex = @"man";
self.age = @"25";
}
return self;
}
接下來我們創(chuàng)建兩個類判斷一下相等看看會有什么結(jié)果:
Person *personA = [[Person alloc] init];
Person *personB = [[Person alloc] init];
NSLog(@"內(nèi)存地址:personA = %p,personB = %p",personA,personB);
NSLog(@"----------直接==比較---------------");
if (personA == personB) {
NSLog(@"personA == personB");
} else {
NSLog(@"personA != personB");
}
日志輸出:
2016-12-07 21:12:00.348 TestString[5998:246004] 內(nèi)存地址:personA = 0x7fb7b2c28570,personB = 0x7fb7b2ca1e40
2016-12-07 21:12:00.348 TestString[5998:246004] ----------直接==比較---------------
2016-12-07 21:12:00.348 TestString[5998:246004] personA != personB
由以上可知师痕,兩個不可能相等溃睹。
然后我們實現(xiàn)一個自定義的判斷相等的方法:- (BOOL)isEqualToPerson:(Person *)person;
- (BOOL)isEqualToPerson:(Person *)person {
if (self == person) {
return YES;
}
if (![self.name isEqualToString:person.name]) {
return NO;
}
if (![self.sex isEqualToString:person.sex]) {
return NO;
}
if (![self.age isEqualToString:person.age]) {
return NO;
}
return YES;
}
####################################
if ([personA isEqualToPerson:personB]) {
NSLog(@"personA == personB");
} else {
NSLog(@"personA != personB");
}
日志:
2016-12-07 21:15:21.489 TestString[5998:246004] personA == personB
到這里我們就實現(xiàn)了兩個對象的相等性判斷胰坟,但是這樣是不行的因篇,這樣子還是沒有解決我們最初的問題。我們還需要實現(xiàn)NSObject協(xié)議中聲明的用于判斷等同性的兩個關(guān)鍵方法:- (BOOL)isEqual:(id)object和- (NSUInteger)hash笔横。我們先看一下- (BOOL)isEqual:(id)object方法竞滓,- (NSUInteger)hash這個方法稍后再說。
- (BOOL)isEqual:(id)object
自建類編寫isEqual方法的一般方式是實現(xiàn)一個自定義的判斷相等性的方法吹缔,如果所比較的參數(shù)對象和接受消息的對象都同屬一個類商佑,那么句調(diào)用自己編寫的自定義方法,否則就交與父類來判斷厢塘,所以Person類的isEqual方法可以這樣實現(xiàn):
- (BOOL)isEqual:(id)object {
if ([self class] == [object class]) {
return [self isEqualToPerson:(Person *)object];
}
return [super isEqual:object];
}
這樣實現(xiàn)以后我們最初的問題就可以解決:
Person *personA = [[Person alloc] init];
Person *personB = [[Person alloc] init];
NSArray *personArrA = @[personA];
if ([personArrA containsObject:personB]) {
NSLog(@"personArrA 中包含 personB");
} else {
NSLog(@"personArrA 中不包含 personB");
}
日志:
2016-12-07 21:24:13.841 TestString[5998:246004] personArrA 中包含 personB
當(dāng)執(zhí)行[personArrA containsObject:personB]時茶没,遍歷數(shù)組中的元素對象和PersonB比較肌幽,自動去調(diào)用Person類中的isEqual方法,然后發(fā)現(xiàn)兩者是同屬一個類抓半,所以調(diào)用isEqualToPerson去判斷兩者的相等性喂急,發(fā)現(xiàn)相等,返回YES笛求。
</br>
還有一個函數(shù)我們也是需要實現(xiàn)的- (NSUInteger)hash煮岁,
- (NSUInteger)hash
對于面向?qū)ο缶幊虂碚f,對象相等性檢查的主要用例涣易,就是確定一個對象是不是集合的成員。為了加快這個進程冶伞,子類當(dāng)中需要實現(xiàn)hash方法新症,這兩個方法的關(guān)系:
1、如果isEqual方法判斷兩個對象相等响禽,那么其hash方法也必須返回同一個值
2徒爹、如果兩個對象的hash方法返回同一個值,那么isEqual方法未必會認(rèn)為兩者相等芋类。
這種現(xiàn)象在Set容器中表現(xiàn)的最為明顯隆嗅,我們先將Person中的hash方法實現(xiàn)設(shè)置為父類的默認(rèn)實現(xiàn):
- (NSUInteger)hash {
NSUInteger hash = [super hash];
NSLog(@"hash值:%lu",hash);
return hash;
}
然后創(chuàng)建兩個Person對象放入到Set容器中
Person *personA = [[Person alloc] init];
Person *personB = [[Person alloc] init];
NSMutableSet *set = [[NSMutableSet alloc] initWithCapacity:1];
[set addObject:personA];
[set addObject:personB];
NSLog(@"%@",[set allObjects]);
日志:
2016-12-07 21:39:10.567 TestString[6080:258148] 內(nèi)存地址:personA = 0x7f7ffbc09a10,personB = 0x7f7ffbca0f80
2016-12-07 21:39:10.568 TestString[6080:258148] hash值:140187661277712
2016-12-07 21:39:10.568 TestString[6080:258148] hash值:140187661897600
2016-12-07 21:39:10.568 TestString[6080:258148] (
"<Person: 0x7f7ffbc09a10>",
"<Person: 0x7f7ffbca0f80>"
)
可以發(fā)現(xiàn):當(dāng)想Set容器中添加元素時,先調(diào)用對象的hash函數(shù)侯繁,比較兩個對象的hash值胖喳,如果hash值不想等,則認(rèn)為這兩個元素對象不相等贮竟,則添加到Set容器中丽焊。
下面,我們修改一下hash函數(shù)咕别,時期返回相同的值:
- (NSUInteger)hash {
NSUInteger nameHash = [self.name hash];
NSUInteger sexHash = [self.sex hash];
NSUInteger ageHash = [self.age hash];
NSUInteger hash = nameHash ^ sexHash ^ ageHash;
// NSUInteger hash = [super hash];
NSLog(@"hash值:%lu",hash);
return hash;
}
從新執(zhí)行一下:
日志:
2016-12-07 21:42:25.393 TestString[6094:259733] 內(nèi)存地址:personA = 0x7fbec2f37600,personB = 0x7fbec2f368e0
2016-12-07 21:42:25.394 TestString[6094:259733] hash值:1233295
2016-12-07 21:42:25.394 TestString[6094:259733] hash值:1233295
2016-12-07 21:42:25.394 TestString[6094:259733] (
"<Person: 0x7fbec2f37600>"
)
這樣就會只添加一個元素技健,因為這兩個元素是相等的。斷點跟蹤一下惰拱,它的執(zhí)行流程為:
1雌贱、先調(diào)用hash函數(shù)獲取對象的hash值,如果兩者的hash值不等偿短,則直接判斷兩個對象不想等欣孤。
2、如果hash值相等翔冀,則調(diào)用isEqual方法作進一步比較导街,如果isEqual返回NO,則認(rèn)為兩對象不相等纤子。
3搬瑰、如果isEqual方法返回YES款票,則任務(wù)兩個對象相等。
還有一種情況也可以加深一下對這兩個函數(shù)的理解:讓對象當(dāng)做字典的鍵泽论。
首先作為字典的鍵需要實現(xiàn)NSCopying協(xié)議艾少,我們對Person類做一下修改:
@interface Person : NSObject<NSCopying> //聲明NSCopying
·
·
·
@end
#import "Person.h"
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *person = [[[self class] allocWithZone:zone] init];
person.name = self.name;
person.sex = self.sex;
person.age = self.age;
return person;
}
·
·
·
@end
然后我們這樣操作:
Person *personA = [[Person alloc] init];
Person *personB = [[Person alloc] init];
NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithCapacity:1];
[dic setObject:@"ljt" forKey:personA];
NSLog(@"%@",[dic objectForKey:personB]);
日志:
2016-12-07 21:52:33.235 TestString[6139:264284] 內(nèi)存地址:personA = 0x7fbe73d13a30,personB = 0x7fbe73d1f790
2016-12-07 21:52:57.146 TestString[6139:264284] hash值:1233295
2016-12-07 21:53:06.623 TestString[6139:264284] hash值:1233295
2016-12-07 21:53:20.339 TestString[6139:264284] ljt
以personA為鍵設(shè)的值,盡然通過personB取出來了!斷點跟蹤我們可以發(fā)現(xiàn)翼悴,字典設(shè)值和取值的時候缚够,先獲取對象的hash值,如果hash值相等鹦赎,則通過isEqual去比較谍椅,如果比較結(jié)果一致,則認(rèn)為是同一個對象古话。設(shè)值的時候如果字典里面沒有雏吭,則加入字典中,如果字典中已經(jīng)存在了相同的鍵陪踩,則重新賦值杖们。
NOTE:字典在設(shè)置Key的時候需要復(fù)制這個Key對象的(這個key值對象需要實現(xiàn)NSCopying協(xié)議),也就是說肩狂,我們的personA在作為key值得時候摘完,已經(jīng)被字典復(fù)制一份了,因為字典需要確保這個key值不可變傻谁,否則孝治,作為key值得對象通過修改內(nèi)部屬性而導(dǎo)致這個對象的hash值發(fā)生變化,會出現(xiàn)找不到對象的情況审磁。
至此:我們可以對Objc中對象的相等比較荆秦,有了一個大致的了解。
參考文獻
1力图、NShipster Equality
2步绸、使用NSArray containObject:方法比較對象
3、重載hash與isEqual:方法