Created
September 30, 2017 17:07
-
-
Save aglee/478ff54fc6c45fb852d7d22de237307b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
// Two things to know about NSSet: | |
// | |
// - NSSet assumes its elements will never mutate. It breaks if they do. | |
// - Unlike NSDictionary, NSSet does not copy its unique objects. It stores | |
// the original pointers. | |
// | |
// Bottom line: DO NOT MUTATE OBJECTS THAT ARE ELEMENTS OF AN NSSET. The | |
// code below demonstrates the kind of bugs you could run into if you | |
// disregard this guideline. The output I got is at the bottom of the file. | |
// | |
// Ways to avoid these problems: | |
// | |
// - Keep set elements simple. For example, use a set of database row ids | |
// instead of entire database row objects. | |
// - Copy the values you add, e.g. `[string1 copy]` instead of `string1`. | |
// This example has the side benefit that `copy` returns an immutable | |
// string, so you can't accidentally mutate the set element. | |
// One way to break an NSSet, simply by mutating one element. | |
void demo1() { | |
NSMutableString *string1 = [NSMutableString stringWithString:@"abc"]; | |
NSMutableString *string2 = [NSMutableString stringWithString:@"abcdef"]; | |
NSLog(@"Strings:"); | |
NSLog(@"%x %@", string1, string1); | |
NSLog(@"%x %@", string2, string2); | |
// Unlike NSDictionary, NSSet does not copy. The pointers listed below | |
// are the same as above. | |
NSSet *set = [NSSet setWithObjects:string1, string2, nil]; | |
NSLog(@"Set:"); | |
for (NSString *str in set) { | |
NSLog(@"%x %@", str, str); | |
} | |
NSLog(@"set count = %d", [set count]); | |
NSLog(@"contains %@? %d", string1, [set containsObject:string1]); | |
// Simply mutate one element to be "abcxxx". According to the output, the | |
// set does not contain "abcxxx", even though that value is listed when you | |
// enumerate the set. | |
[string1 appendString:@"xxx"]; | |
NSLog(@"MUTATED string1"); | |
NSLog(@"Strings:"); | |
NSLog(@"%x %@", string1, string1); | |
NSLog(@"%x %@", string2, string2); | |
NSLog(@"Set:"); | |
for (NSString *str in set) { | |
NSLog(@"%x %@", str, str); | |
} | |
NSLog(@"set count = %d", [set count]); | |
for (NSString *str in @[@"abc", @"abcxxx"]) { | |
NSLog(@"contains %@? %d", str, [set containsObject:str]); | |
} | |
} | |
// Variation of demo1() that only has one element in the set. In this case | |
// the output *does* correctly report after mutation that the set contains | |
// "abcxxx". This could burn you if you never tested your code with sets | |
// containing more than one element. | |
void demo2() { | |
NSMutableString *string1 = [NSMutableString stringWithString:@"abc"]; | |
NSLog(@"Strings:"); | |
NSLog(@"%x %@", string1, string1); | |
// Unlike NSDictionary, NSSet does not copy. The pointers listed below | |
// are the same as above. | |
NSSet *set = [NSSet setWithObjects:string1, nil]; | |
NSLog(@"Set:"); | |
for (NSString *str in set) { | |
NSLog(@"%x %@", str, str); | |
} | |
NSLog(@"set count = %d", [set count]); | |
NSLog(@"contains %@? %d", string1, [set containsObject:string1]); | |
// Mutate one element to be "abcxxx". | |
[string1 appendString:@"xxx"]; | |
NSLog(@"MUTATED string1"); | |
NSLog(@"Strings:"); | |
NSLog(@"%x %@", string1, string1); | |
NSLog(@"Set:"); | |
for (NSString *str in set) { | |
NSLog(@"%x %@", str, str); | |
} | |
NSLog(@"set count = %d", [set count]); | |
for (NSString *str in @[@"abc", @"abcxxx"]) { | |
NSLog(@"contains %@? %d", str, [set containsObject:str]); | |
} | |
} | |
// Another way to break an NSSet, by mutating one element to equal another. | |
void demo3() { | |
NSMutableString *string1 = [NSMutableString stringWithString:@"abc"]; | |
NSMutableString *string2 = [NSMutableString stringWithString:@"abcdef"]; | |
NSLog(@"Strings:"); | |
NSLog(@"%x %@", string1, string1); | |
NSLog(@"%x %@", string2, string2); | |
// Unlike NSDictionary, NSSet does not copy. The pointers listed below | |
// are the same as above. | |
NSSet *set = [NSSet setWithObjects:string1, string2, nil]; | |
NSLog(@"Set:"); | |
for (NSString *str in set) { | |
NSLog(@"%x %@", str, str); | |
} | |
NSLog(@"set count = %d", [set count]); | |
NSLog(@"contains %@? %d", string1, [set containsObject:string1]); | |
// Mutate string1 to match string2. According to the output, the set's | |
// count is still 2, and enumerating it lists 2 elements, even though it | |
// contains only one unique value. | |
[string1 appendString:@"def"]; | |
NSLog(@"MUTATED string1 to match string2"); | |
NSLog(@"Strings:"); | |
NSLog(@"%x %@", string1, string1); | |
NSLog(@"%x %@", string2, string2); | |
NSLog(@"Set:"); | |
for (NSString *str in set) { | |
NSLog(@"%x %@", str, str); | |
} | |
NSLog(@"set count = %d", [set count]); | |
NSLog(@"contains abc? %d", [set containsObject:@"abc"]); | |
NSLog(@"contains abcdef? %d", [set containsObject:@"abcdef"]); | |
} | |
int main(int argc, char *argv[]) { | |
@autoreleasepool { | |
NSLog(@"---- demo1 ----"); | |
demo1(); | |
NSLog(@"\n\n\n"); | |
NSLog(@"---- demo2 ----"); | |
demo2(); | |
NSLog(@"\n\n\n"); | |
NSLog(@"---- demo3 ----"); | |
demo3(); | |
} | |
} | |
// The output I got: | |
// | |
// ---- demo1 ---- | |
// Strings: | |
// 67400520 abc | |
// 674007c0 abcdef | |
// Set: | |
// 67400520 abc | |
// 674007c0 abcdef | |
// set count = 2 | |
// contains abc? 1 | |
// MUTATED string1 | |
// Strings: | |
// 67400520 abcxxx | |
// 674007c0 abcdef | |
// Set: | |
// 67400520 abcxxx | |
// 674007c0 abcdef | |
// set count = 2 | |
// contains abc? 0 | |
// contains abcxxx? 0 | |
// | |
// | |
// | |
// ---- demo2 ---- | |
// Strings: | |
// 677042a0 abc | |
// Set: | |
// 677042a0 abc | |
// set count = 1 | |
// contains abc? 1 | |
// MUTATED string1 | |
// Strings: | |
// 677042a0 abcxxx | |
// Set: | |
// 677042a0 abcxxx | |
// set count = 1 | |
// contains abc? 0 | |
// contains abcxxx? 1 | |
// | |
// | |
// | |
// ---- demo3 ---- | |
// Strings: | |
// 67706370 abc | |
// 677063b0 abcdef | |
// Set: | |
// 67706370 abc | |
// 677063b0 abcdef | |
// set count = 2 | |
// contains abc? 1 | |
// MUTATED string1 to match string2 | |
// Strings: | |
// 67706370 abcdef | |
// 677063b0 abcdef | |
// Set: | |
// 67706370 abcdef | |
// 677063b0 abcdef | |
// set count = 2 | |
// contains abc? 0 | |
// contains abcdef? 1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment