atxe
atxe

Reputation: 5077

NSMutableSet removeObject not working as expected

I am working with a NSMutableSet that contains objects that mutable objects. I am finding inconsistencies on NSMutableSet when trying to remove an object that has been mutated.

In order to isolate the problem I have coded a quick test to illustrate the issue (if any):

Example 1: Working as I expected

NSMutableSet *colors = [[NSMutableSet alloc] init];
NSMutableString *color1 = [[NSMutableString alloc] initWithString:@"Blue"];
NSMutableString *color2 = [[NSMutableString alloc] initWithString:@"Green"];
[colors addObject:color1];
[colors addObject:color2];
NSLog(@"%@",[colors description]); // {( Green, Blue )}
[color1 insertString:@"Cobalt " atIndex:0]; 
NSLog(@"%@",[colors description]); // {( Green, "Cobalt Blue" )}
[colors removeObject:color1];
NSLog(@"%@",[colors description]); {( Green )}

Example 2: NOT working as I expected

NSMutableSet *surnames = [[NSMutableSet alloc] init];
NSMutableString *surname1 = [[NSMutableString alloc] initWithString:@"Brown"];
NSMutableString *surname2 = [[NSMutableString alloc] initWithString:@"Homes"];
[surnames addObject:surname1];
[surnames addObject:surname2];
NSLog(@"%@",[surnames description]); // {( Homes, Brown )}

[surname1 appendString:@"ie"];
NSLog(@"%@",[surnames description]); // {( Homes, Brownie )}
[surnames removeObject:surname1];
NSLog(@"%@",[surnames description]); // {( Homes, Brownie )}

NSString *surnameToRemove = nil;
for (NSString *surname in surnames) {
    if ([surname isEqualToString:@"Brownie"]) {
        surnameToRemove = surname;
        break;
    }
}
[surnames removeObject:surnameToRemove];
NSLog(@"%@",[surnames description]); // {( Homes, Brownie )}

As shown in Example 2, after mutating surname1, removeObject is not removing it, even after taking the reference by searching it. Why this is happening? Can't the mutable containers contain mutable objects?

I have read in this post that NSSet caches the hashes of the contained objects and that might be the issue. If so, is there a way to clean it up? Any alternative solution?

Just for the sake of curiosity, why Example 1 is working?

UPDATE:

From Apple's Collection Programming Topics:

If mutable objects are stored in a set, either the hash method of the objects shouldn’t depend on the internal state of the mutable objects or the mutable objects shouldn’t be modified while they’re in the set. For example, a mutable dictionary can be put in a set, but you must not change it while it is in there. (Note that it can be difficult to know whether or not a given object is in a collection).

UPDATE 2:

Important, Example 2 is returning different log outputs if you running on a Mac or iOS app:

Log in a Mac application (working as I expected):

{( Green, Blue )}
{( Green, "Cobalt Blue" )}
{( Green )}
{( Brown, Homes )}
{( Brownie, Homes )}
{( Homes )}
-[__NSSetM removeObject:]: object cannot be nil

Log in a iOS application (not working as I expected):

{( Green, Blue )}
{( Green, "Cobalt Blue" )}
{( Green )}
{( Homes, Brown )}
{( Homes, Brownie )}
{( Homes, Brownie )}
{( Homes, Brownie )}

UPDATE 3:

Same code than in Example 2 but with NSMutableArray seems to work... so guessing how NSMutableSet is working with hashes. I believe as commented in the linked thread above it's caching them:

NSMutableArray *surnames = [[NSMutableArray alloc] init];
NSMutableString *surname1 = [[NSMutableString alloc] initWithString:@"Brown"];
NSMutableString *surname2 = [[NSMutableString alloc] initWithString:@"Homes"];
[surnames addObject:surname1];
[surnames addObject:surname2];
NSLog(@"%@",[surnames description]);  // {( Homes, Brown )}
[surname1 appendString:@"ie"];
NSLog(@"%@",[surnames description]);  // {( Homes, Brownie )}
[surnames removeObject:surname1];
NSLog(@"%@",[surnames description]);  // {( Homes )}

Upvotes: -1

Views: 941

Answers (0)

Related Questions