UIMutableIndexPath and NSIndexPath return true for isEqual: but have different hash values

Originator:levigroker
Number:rdar://18094838 Date Originated:21-Aug-2014 04:21 PM
Status:Open Resolved:
Product:iOS SDK Product Version:7.1
Classification: Reproducible:Always
 
Summary:
In iOS7, the hash implementation for UIMutableIndexPath and NSIndexPath appear to have changed. If you have a mutable and non-mutable index path with the same value, they return true for isEqual: but have differing hash values. In iOS6 the hash values would be equal.

The UITableViewDelegate method - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath provides a UIMutableIndexPath. If, for example, the program keeps an NSSet of NSIndexPaths and checks the membership of the supplied indexPath, there will be false negatives due to the hash difference.

Steps to Reproduce:
Run the attached Xcode project, or:

Implement the UITableViewDelegate method
 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

with the following code (or similar):
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat retVal = 50.0f;

    NSIndexPath *copiedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];

    NSLog(@"=============== heightForRowAtIndexPath: =============== ");
    NSLog(@"copiedIndexPath: '%@' indexPath: '%@'", copiedIndexPath, indexPath);
    NSLog(@"[copiedIndexPath class]: '%@' [indexPath class]: '%@'", [copiedIndexPath class], [indexPath class]);
    NSLog(@"isEqual: %@", [indexPath isEqual:copiedIndexPath] ? @"YES" : @"NO");
    NSLog(@"[copiedIndexPath hash]: '%u' [indexPath hash]: '%u'", [copiedIndexPath hash], [indexPath hash]);

    if ([indexPath isEqual:copiedIndexPath] && indexPath.hash != copiedIndexPath.hash)
    {
        NSLog(@"ERROR: If isEqual: evaluates to YES then both object's hash values should also be equal.");
    }
    
    return retVal;
}


Expected Results:
The if statement would never evaluate to true and no "ERROR" log statement would be output.

Actual Results:
For all indexPath's greater than [0,0] the if evaluates to true and an error was output:

2014-08-21 16:06:09.369 IndexPathBug[69261:60b] =============== heightForRowAtIndexPath: =============== 
2014-08-21 16:06:09.370 IndexPathBug[69261:60b] copiedIndexPath: '<NSIndexPath: 0x8c7c160> {length = 2, path = 0 - 0}' indexPath: '<UIMutableIndexPath 0x8c7c050> 2 indexes [0, 0]'
2014-08-21 16:06:09.370 IndexPathBug[69261:60b] [copiedIndexPath class]: 'NSIndexPath' [indexPath class]: 'UIMutableIndexPath'
2014-08-21 16:06:09.370 IndexPathBug[69261:60b] isEqual: YES
2014-08-21 16:06:09.371 IndexPathBug[69261:60b] [copiedIndexPath hash]: '22' [indexPath hash]: '22'
2014-08-21 16:06:09.371 IndexPathBug[69261:60b] =============== heightForRowAtIndexPath: =============== 
2014-08-21 16:06:09.371 IndexPathBug[69261:60b] copiedIndexPath: '<NSIndexPath: 0xa5b08a0> {length = 2, path = 0 - 1}' indexPath: '<UIMutableIndexPath 0xa56abc0> 2 indexes [0, 1]'
2014-08-21 16:06:09.371 IndexPathBug[69261:60b] [copiedIndexPath class]: 'NSIndexPath' [indexPath class]: 'UIMutableIndexPath'
2014-08-21 16:06:09.372 IndexPathBug[69261:60b] isEqual: YES
2014-08-21 16:06:09.372 IndexPathBug[69261:60b] [copiedIndexPath hash]: '16406' [indexPath hash]: '22'
2014-08-21 16:06:09.372 IndexPathBug[69261:60b] ERROR: If isEqual: evaluates to YES then both object's hash values should also be equal.
2014-08-21 16:06:09.373 IndexPathBug[69261:60b] =============== heightForRowAtIndexPath: =============== 
2014-08-21 16:06:09.373 IndexPathBug[69261:60b] copiedIndexPath: '<NSIndexPath: 0xa577d60> {length = 2, path = 0 - 2}' indexPath: '<UIMutableIndexPath 0xa56d2d0> 2 indexes [0, 2]'
2014-08-21 16:06:09.373 IndexPathBug[69261:60b] [copiedIndexPath class]: 'NSIndexPath' [indexPath class]: 'UIMutableIndexPath'
2014-08-21 16:06:09.373 IndexPathBug[69261:60b] isEqual: YES
2014-08-21 16:06:09.373 IndexPathBug[69261:60b] [copiedIndexPath hash]: '32790' [indexPath hash]: '16406'
2014-08-21 16:06:09.374 IndexPathBug[69261:60b] ERROR: If isEqual: evaluates to YES then both object's hash values should also be equal.

etc.

Interestingly, the hashes for the mutable and non-mutable seem to be "off by one" (if you compare the various row values you will note that the previous row value will have the same hash...)

For instance:

[0, 4]
UIMutableIndexPath hash: '98326'
NSIndexPath hash: '131094'
[0, 5]
UIMutableIndexPath hash: '131094'
NSIndexPath hash: '163862'

Note, the [0, 4] NSIndexPath hash is the same as the [0, 5] UIMutableIndexPath hash (both are 131094).

Version:
iOS SDK 7.1

Notes:


Configuration:
All iOS 7 simulators.

Attachments:
'IndexPathBug.zip' was successfully uploaded.

Dupe of 15806671

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!