IGListDiffable and Equality

This guide details how to write good isEqual: methods.

Background

IGListKit requires that models implement the method isEqualToDiffableObject: which should perform the same type of check as isEqual:, but without impacting performance characteristics like in Objective-C containers such as NSDictionary and NSSet.

IGListDiffable bare minimum

The quickest way to get started with diffable models is use the object itself as the identifier, and use the superclass’s -[NSObject isEqual:] implementation for equality:

- (id<NSObject>)diffIdentifier {
  return self;
}

- (BOOL)isEqualToDiffableObject:(id<IGListDiffable>)object {
  return [self isEqual:object];
}

Writing better Equality methods

Even though IGListKit uses the method isEqualToDiffableObject:, the concepts of writing a good equality check apply in general. Here are the basics to writing good -isEqual: and -hash functions. Note this is all Objective-C but applies to Swift also.

  • If you override -isEqual: you must override -hash. Check out this article by Mike Ash for details.
  • Always compare the pointer first. This saves a lot of wasteful objc_msgSend(...) calls and value comparisons if checking the same instance.
  • When comparing object values, always check for nil before -isEqual:. For example, [nil isEqual:nil] unintuitively returns NO. Instead, do left == right || [left isEqual:right].
  • Always compare the cheapest values first. For example, doing [self.array isEqual:other.array] && self.intVal == other.array is extremely wasteful if the intVal values are different. Use lazy evaluation!

As an example, if I had a User model with the following interface:

@interface User : NSObject

@property NSInteger identifier;
@property NSString *name;
@property NSArray *posts;

@end

You would implement its equality methods like so:

@implementation User

- (NSUInteger)hash {
  return self.identifier;
}

- (BOOL)isEqual:(id)object {
  if (self == object) { 
      return YES;
  }

  if (![object isKindOfClass:[User class]]) {
      return NO;
  }

  User *right = object;
  return self.identifier == right.identifier 
      && (self.name == right.name || [self.name isEqual:right.name])
      && (self.posts == right.posts || [self.posts isEqualToArray:right.posts]);
}

@end

Using both IGListDiffable and -isEqual:

Making your objects work universally with Objective-C containers and IGListKit is easy once you’ve implemented isEqual: and -hash.

@interface User <IGListDiffable>

// properties...

@end

@implementation User

- (id<NSObject>)diffIdentifier {
    return @(self.identifier);
}

- (BOOL)isEqualToDiffableObject:(id<IGListDiffable>)object {
    return [self isEqual:object];
}

@end