title: Why don't NSSet/NSMutableSet/NSCountedSet force immutable objects as entries?
body: <p>NSDictionary keys are id&lt;NSCopying&gt; but the value for a set is just id, and the docs indicate their values are retained.  According to the <a href="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Collections/Articles/Sets.html#//apple_ref/doc/uid/20000136-SW1" rel="nofollow">Set Fundamentals of the Collection Programming Topics</a> docs:</p>
  
  <blockquote>
    <p>You can, however, modify individual objects themselves (if they support modification). </p>
  </blockquote>
  
  <p>If you modify an object, this could affect the hashvalue of the object, which would affect lookups.  I assumed that an NSSet is a fast lookup?</p>
  
  <p>Here's an example that shows how things break if you mutate objects:</p>
  
  <pre><code>    NSMutableString *str = [NSMutableString stringWithString: @"AWESOME"];
      NSCountedSet *countedSet = [[NSCountedSet alloc] init];
      [countedSet addObject: str];
      [countedSet addObject: str];
  
      NSLog(@"%@", @([countedSet countForObject: @"AWESOME"]));
  
      [str appendString: @" NOT AWESOME"];
      NSLog(@"%@", @([countedSet countForObject: @"AWESOME NOT AWESOME"]));
      NSLog(@"%@", @([countedSet countForObject: @"AWESOME"]));
      NSLog(@"%@", @([countedSet countForObject: str]));
  
      for(NSString *s in countedSet) {
          NSLog(@"%@ - %@", str, @([countedSet countForObject: s]));
      }
  
      NSSet *set = [NSSet setWithArray: @[ str ]];
      NSLog(@"Set Contains string, %@", @([set containsObject: str]));
      [str appendString: @"asdf"];
      NSLog(@"Set Contains string, %@", @([set containsObject: str]));
      NSLog(@"%@", set);
  </code></pre>
  
  <p>And output with my interpretation:</p>
  
  <pre><code>[64844:303] 2          // Count is 2
  [64844:303] 0          // Count should be 2 - if it looks for the literal string
  [64844:303] 0          // Count should be 0, but can't find original object either
  [64844:303] 0          // Count should be 2 - asking for actual object that's in there
  [64844:303] AWESOME NOT AWESOME - 0   // Should be 2 - asking for actual object that it just retrieved
  [64844:303] Set Contains string, 1    // Correct, pre-mutation
  [64844:303] Set Contains string, 0    // Should be true, object is in there
  [65070:303] {(
      "AWESOME NOT AWESOMEasdf"   // see?  It's in there
  )}
  </code></pre>
  
  <p>My take:</p>
  
  <p>The set likely buckets based on hash value, when the hash is changed out behind the set, it doesn't know what to do and lookups are broken.  The documentation is lacking in this area.</p>
  
  <p>My question restated:
  Docs say you can mutate objects, which is not intuitive.
  Mutating objects breaks sets.
  WTF?</p>