rentzsch.com: tales from the red shed

Cocoa/CoreFoundation Collection Capacity Conflict

Cocoa

Don’t you love alliteration?

Coconspirator Len Case recently killed a bug. Fortunately I saw his commit message fly by and learned something. Witness:

#if CRASH_CITY
  NSMutableSet *set = (NSMutableSet*)CFSetCreateMutable(kCFAllocatorDefault, 10, &kCFTypeSetCallBacks);
#else
  NSMutableSet *set = [[NSMutableSet alloc] initWithCapacity:10];
#endif
  for(int i = 0; i < 26; i++) {
    printf("%d ", i);
    [set addObject:[NSString stringWithFormat:@"%d", i]];
  }

Set CRASH_CITY to 0 if you want the program to complete successfully: printing out numbers 0 through 25.

Set it to 1 if you’d prefer it to only get to 24 and then croak with a SIGBUS.

The crash illustrates the difference in handling collection capacity between Cocoa and Core Foundation. Even when they’re ostensibly the same.

(This example uses NSMutableSet/CFMutableSetRef, but the issue also affects NSMutableArray/CFMutableArrayRef and NSMutableDictionary/CFMutableDictionaryRef.)

When creating a collection using Cocoa, capacity is advisory. If you stuff more objects into a collection than its capacity — as this code snippet does — you may suffer a performance hit, but your program will continue to work.

This stands in contrast to collection creation using Core Foundation: its capacity is strict. Try to put more objects into a Core Foundation-created collection, and you won’t even get an exception — you’ll get but an ol’timey crash.

Unfortunately, while Core Foundation’s capacity handling is strict, it’s also sloppy. By all rights, with CRASH_CITY set to 1, our program should have never made it to 10. But it made it all the way to 24 before it ran out of road.

That’s because Core Foundation silently internally increases the capacity you’ve handed it, to match some number Apple has decided results in better performance. (Here I would have accused Apple of Sharking lots o’ CF code to determine the optimum number, but they seem way too human-rounded to me. CFMutableSetRef rounds 10 up to 25, CFMutableArrayRef rounds it up to 100 while CFMutableDictionaryRef rounds up to an even 10000. For chrissake those aren’t even powers of two!)

Core Foundation’s capacity slop makes it depressingly easy to introduce an off-by-one (or even off-by-a-dozen) when specifying a capacity. Technically, your program is incorrect, but you’ll never notice since CF has covered up your mistake.

Because Core Foundation collections have a nasty habit of making their way up to Cocoa-land as seemingly innocent mutable collections (that won’t blow up if you happen to put one more object into them), it’s probably best to avoid specifying a nonzero capacity when calling CF(Set|Array|Dictionary)CreateMutable(). Unless Shark is telling you you’re dying from collection buffer reallocation, it seems wise to just avoid the danger altogether.

Wednesday, December 27, 2006
07:37 PM