最近在新接手的項目中進行對象比較,對同一個對象調用isEqual來比較,結果居然是NO。猜測是對象重寫了isEqual
方法。查看代碼以下:算法
isEqual
方法,雖然方法不太嚴謹,沒有首先判斷==,代碼看起來也什麼大問題,可是同一個對象比較也不該該返回NO啊。看了下面一堆&&判斷,難道要一個個po嗎?忽然想到了
二分查找
算法(算法基礎仍是有用的)的優勢,而後快速定位到放回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中聲明的,默認實現就是簡單的比較對象地址。數據結構
@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查找某個成員的過程就是ui
HashTable是一種基本數據結構,NSSet和NSDictionary都是使用HashTable存儲數據的,所以能夠能夠確保他們查詢成員的速度爲O(1)。而NSArray使用了順序表存儲數據,查詢數據的時間複雜度爲O(n)。atom
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文件spa
#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
複製代碼