最近在新接手的項目中進行對象比較,對同一個對象調用isEqual來比較,結果居然是NO。猜測是對象重寫了isEqual
方法。查看代碼以下:算法
isEqual
方法,雖然方法不太嚴謹,沒有首先判斷==,代碼看起來也什麼大問題,可是同一個對象比較也不該該返回NO啊。看了下面一堆&&判斷,難道要一個個po嗎?忽然想到了
二分查找
算法(算法基礎仍是有用的)的優勢,而後快速定位到放回NO的條件。
而後繼續根據二分查找,最終找到self.releasetime比較的時候返回NO。原來releasetime字段爲nil,使用isEqualToString比較兩個爲nil的對象的時候返回NO。
(lldb) po (BOOL)([self.releasetime isEqualToString:info.releasetime])
NO
(lldb)
複製代碼
解決辦法:編程
場景一:針對當前的場景,只須要在的最頂部添加下面判斷就能夠了
if (self == object) return YES;
數組
場景二:可是一些其餘場景,咱們確實會遇到比較兩個地址不同,可是數據同樣(而且對象有的屬性確實爲
nil
)的場景。這個時候咱們能夠爲NSString
等基本數據類型添加category
,而後重寫isEqualToString:
方法,若是要比較的兩個對象都是nil
則返回YES
。可是若是要比較的兩個對象都不是nil並且length
也相同,難道我還要重寫一個方法進行遍歷比較嗎?能不能直接用原來的isEqualToString:
方法,可是category
中不能調用super
方法,再說分類也不是子類。此時咱們就須要利用runtime
遍歷方法列表找到原來的方法,而後調用一下返回結果就能夠了。後來發現,想多了,給nil發送消息返回的始終是NO。最後在分類中實現一個類方法判斷一下就能夠了。bash
#import "NSString+isEqual.h" @implementation NSString (isEqual) +(void)load { NSLog(@"結果1 = %@", [nil isEqualToString:nil] ? @"YES" : @"NO"); NSLog(@"結果2 = %@", [NSString isString:nil EqualToString:nil] ? @"YES" : @"NO"); } +(BOOL)isString:(NSString *)aString EqualToString:(NSString *)bString { if (aString == nil && bString == nil ) { return YES; } return [aString isEqualToString:bString]; } @end 複製代碼
基本類型
, ==運算符比較的是值;對象類型
, ==運算符比較的是對象的地址isEqual
方法就是用來判斷兩個對象是否相等(自定義對象須要重寫isEqual)isEqual方法是NSObject中聲明的,默認實現就是簡單的比較對象地址。markdown
@implementation NSObject (Approximate) - (BOOL)isEqual:(id)object { return self == object; } @end 複製代碼
-(BOOL)isEqual:(id)object
-(NSUInteger)hash
-(id)copyWithZone:(NSZone *)zone
注意: 使用OC自定義的類實例做爲NSDictionary的key的話,須要實現NSCoping協議,若是不實現的話,向dictionary中添加數據時會報警告:Sending 'Coder *__strong to parameter of incompatible type 'id _Nonnull',在運行的時候,在setObject forKey函數這裏直接崩潰數據結構
-isEqualToAttributedString:
-isEqualToData:
-isEqualToDate:
-isEqualToDictionary:
-isEqualToHashTable:
-isEqualToIndexSet:
-isEqualToNumber:
-isEqualToOrderedSet:
-isEqualToSet:
-isEqualToString:
-isEqualToTimeZone:
-isEqualToValue:
containsObject:
和removeObject:
方法都是使用了isEqual來判斷成員是否相等的isEqual
繼續判斷是否爲兩個相同對象目的:爲了提升查找的速度函數
和數組相比, 基於hash值索引的Hash Table查找某個成員的過程就是oop
HashTable是一種基本數據結構,NSSet和NSDictionary都是使用HashTable存儲數據的,所以能夠能夠確保他們查詢成員的速度爲O(1)。而NSArray使用了順序表存儲數據,查詢數據的時間複雜度爲O(n)。優化
Coder* coder1 = [Coder initWith:@"C++" level:@"11"]; Coder* coder2 = [Coder initWith:@"C++" level:@"11"]; Coder* coder3 = [Coder initWith:@"C++" level:@"17"]; NSSet* coderSet = [NSSet setWithObjects:coder1, coder2, coder3, nil]; NSLog(@"coderSet.count = %ld", coderSet.count); 複製代碼
Coder* coder1 = [Coder initWith:@"C++" level:@"11"]; Coder* coder2 = [Coder initWith:@"C++" level:@"11"]; Coder* coder3 = [Coder initWith:@"C++" level:@"17"]; NSMutableDictionary* coderDic2 = [NSMutableDictionary dictionary]; [coderDic2 setObject:@"1" forKey:coder1]; [coderDic2 setObject:@"2" forKey:coder2]; [coderDic2 setObject:@"3" forKey:coder3]; NSLog(@"coderDic2.count = %ld", coderDic2.count); 複製代碼
isEqual
進行判斷是否真的相等.h文件atom
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Coder : NSObject<NSCopying> @property (nonatomic, strong) NSString *language; @property (nonatomic, strong) NSString *level; +(instancetype)initWith:(NSString*)language level:(NSString*)level; @end NS_ASSUME_NONNULL_END 複製代碼
.m文件
#import "Coder.h" @implementation Coder +(instancetype)initWith:(NSString*)language level:(NSString*)level { Coder* coder = [[Coder alloc] initWith:language level:level]; return coder; } -(instancetype)initWith:(NSString*)language level:(NSString*)level { self.language = language; self.level = level; return self; } // 對象用做NSSDictionary的key必須實現 -(id)copyWithZone:(NSZone *)zone { Coder* coder = [[Coder allocWithZone:zone] initWith:self.language level:self.level]; return coder; } -(BOOL)isEqual:(id)object { NSLog(@"func = %s", __func__); if (self == object) { return YES; } if (![object isKindOfClass:[self class]]) { // object == nil 在此返回NO return NO; } return [self isEqualToCoder:object]; } -(BOOL)isEqualToCoder:(Coder*)object { // isEqualToString 須要使用分類重寫一下,不然 [nil isEqualToString:nil]會返回NO if (![self.language isEqualToString:object.language]) { return NO; } if (![self.level isEqualToString:object.level]) { return NO; } return YES; } -(NSUInteger)hash { BOOL isCareAddress = YES; NSUInteger hashValue = 0; if (isCareAddress) { // 若是指望對地址不一樣、可是內容相同的對象作區分 hashValue = [super hash]; // 結果:兩個地址不一樣,可是內容相同的對象添加到NSMutableSet中,NSMutableSet的個數返回的是2 } else { // 不關心地址是否相同,只對內容進行區分(對關鍵屬性的hash值進行按位異或運算做爲hash值) hashValue = [self.language hash] ^ [self.level hash]; // 結果:兩個地址不一樣,可是內容相同的對象添加到NSMutableSet中,NSMutableSet的個數返回的是1 } NSLog(@"func = %s, hashValue = %ld", __func__, hashValue); return hashValue; } @end 複製代碼
調用
#import "HashViewController.h" #import "Coder.h" @interface HashViewController () @end @implementation HashViewController - (void)viewDidLoad { [super viewDidLoad]; Coder* coder1 = [Coder initWith:@"C++" level:@"11"]; Coder* coder2 = [Coder initWith:@"C++" level:@"11"]; Coder* coder3 = [Coder initWith:@"C++" level:@"17"]; NSArray* coderList = @[coder1, coder2, coder3]; NSLog(@"array-containsObject-coder1-start"); [coderList containsObject:coder1]; NSLog(@"array-containsObject-coder1-end"); NSLog(@"array-containsObject-coder2-start"); [coderList containsObject:coder2]; NSLog(@"array-containsObject-coder2-end"); NSLog(@"array-containsObject-coder3-start"); [coderList containsObject:coder3]; NSLog(@"array-containsObject-coder3-end"); NSLog(@"coderList.count = %ld", coderList.count); NSSet* coderSet = [NSSet setWithObjects:coder1, coder2, coder3, nil]; NSLog(@"coderSet.count = %ld", coderSet.count); NSDictionary* coderDic = @{@"1":coder1, @"2":coder2, @"3":coder3}; NSLog(@"coderDic.count = %ld", coderDic.count); NSMutableDictionary* coderDic2 = [NSMutableDictionary dictionary]; [coderDic2 setObject:@"1" forKey:coder1]; [coderDic2 setObject:@"2" forKey:coder2]; [coderDic2 setObject:@"3" forKey:coder3]; NSLog(@"coderDic2.count = %ld", coderDic2.count); } @end 複製代碼