Recently, I was comparing objects in a new project. I called isEqual to compare the same object, and the result was NO. The guess is that the object overrides the isEqual method. See the code below:

isEqual
Binary search

(lldb) po (BOOL)([self.releasetime isEqualToString:info.releasetime])
NO
(lldb) 
Copy the code

Solutions:

If (self == object) return YES; If (self == object) return YES;

Scenario 2: But there are other scenarios where we do have comparisons where two addresses are different, but the data is the same (and the object does have properties that are nil). At this point we can add a category for basic data types like NSString, and then override the isEqualToString: method to return YES if both objects we’re comparing are nil. But if I’m comparing two objects that are not nil and the length is the same, do I have to rewrite a method to iterate over the comparison? Can you just use the old isEqualToString: method, but you can’t call super in a category, and the categories aren’t subclasses. At this point, we need to use the Runtime to find the original method list, and then call the return result. And it turns out, overthinking it, that sending a message to nil always returns NO. Finally in the classification of the implementation of a class method to judge it can be.

#import "NSString+isEqual.h"

@implementation NSString (isEqual)

+(void)load {
    NSLog(@"Result 1 = %@", [nil isEqualToString:nil] ? @"YES" : @"NO");
    NSLog(@"Result 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
Copy the code

What is the difference between == and isEqual?

  • forBasic types ofThe == operator compares values;
  • forObject typeThe == operator compares the addresses of objects
  • isEqualThe isEqual method is used to determine whether two objects are equal.

Thought two: the default implementation of isEqual

The isEqual method is declared in NSObject, and the default implementation is simply to compare object addresses.

@implementation NSObject (Approximate)
- (BOOL)isEqual:(id)object {
  return self == object;
}
@end
Copy the code

Thought 3: Several methods to override to customize subclasses that inherit NSObject

  • rewrite-(BOOL)isEqual:(id)object
  • rewrite-(NSUInteger)hash
  • Implementation of the NSCoping protocol is not required-(id)copyWithZone:(NSZone *)zone

NSCoping protocol will be implemented if an oc-defined class instance is used as the NSDictionary key. If this is not implemented, a warning will be generated when adding data to the dictionary: Sending ‘Coder *__strong to parameter of incompatible type ‘id _Nonnull’, which crashed directly in the setObject forKey function at runtime

Think 4: subclasses of NSObject in Foundation have defined their own isEqual

  • NSAttributedString -isEqualToAttributedString:
  • NSData -isEqualToData:
  • NSDate -isEqualToDate:
  • NSDictionary -isEqualToDictionary:
  • NSHashTable -isEqualToHashTable:
  • NSIndexSet -isEqualToIndexSet:
  • NSNumber -isEqualToNumber:
  • NSOrderedSet -isEqualToOrderedSet:
  • NSSet -isEqualToSet:
  • NSString -isEqualToString:
  • NSTimeZone -isEqualToTimeZone:
  • NSValue -isEqualToValue:

Question 5: when is overridden isEqual called

  • 1, NSArraycontainsObject:andremoveObject:All methods use isEqual to determine whether members are equal
  • 2. When the hash method is not perfectly designed and two objects return the same hash value, it is calledisEqualProceed to determine whether two objects are the same

Thought 6: Why hash

Purpose: To improve the speed of search

  • 1. In the case of unsorted arrays, the time complexity of the search is O(n).
  • 2. When a member is added to the Hash Table, it is assigned a Hash value to identify the position of the member in the set. This position identifier can optimize the search time to O(1). Of course, if multiple members have the same location identifier, the search cannot reach O(1).
  • 3. The assigned hash value (that is, the location identifier used to find the members of the collection) is computed using the hash method, and the hash method should preferably return a unique hash value

A hash Table that looks up a member based on a hash index, as opposed to an array, does

  • Step 1: Use the hash value to find the location of the target
  • Step 2: If there are multiple hash value members in the target position, search the hash value in array mode

Question 7: When is hash called

HashTable is a basic data structure. Both NSsets and NSDictionaries use HashTable to store data, so they can ensure that the speed of querying members is O(1). NSArray uses sequential tables to store data, and the time complexity of querying data is O(n).

  • 1. The hash method is called when an object is added to an NSSet
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);
Copy the code
  • 2. The hash method is called when the object is used as the key of the NSDictionary
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);
Copy the code

Question 8: hash and isEqual

  • 1. If two objects are equal, their hash value must be equal
  • 2. If the hash value of two objects is equal (because the hash algorithm is not perfect), they are not necessarily equalisEqualTo see if it’s really equal

Thought # 9: Rewrite the principles of hash

  • The hash method cannot return a constant. Because this value is used as the key of the hash table, 100% of the hash table collisions will occur.
  • The hash of an object instance should be deterministic. The design of the hash method directly affects the efficiency of the search.

Post code

.h files

#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
Copy the code

.m files

#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;
    returnself; } // The object used as the key of the NSSDictionary must implement -(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 == nilreturn NO;
    }
    return[self isEqualToCoder:object]; } -(BOOL)isEqualToCoder:(Coder*)object {// isEqualToString (nil isEqualToString:nil) returns NOif(! [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) {// If you want to differentiate objects with different addresses but the same contenthashValue = [super hash]; // Add NSMutableSet to NSMutableSet;else{// Don't care if the address is the same, only the content is differentiated (for key attributes)hashValue to perform bitwise xor operations ashashValue)hashValue = [self.language hash] ^ [self.level hash]; // Result: two objects with different addresses but the same content are added to NSMutableSet. The number of NSMutableSet returns 1} NSLog(@)"func = %s, hashValue = %ld", __func__, hashValue);
    return hashValue;
}
@end
Copy the code

call

#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
Copy the code

Reference blog:

Objective-c — isEqual and hash

Zen and the Art of Objective-C programming