- Testing Equality
- Custom Subclassing
- Reducing Message-Sending
- Copying and Mutability
- Avoid Autoreleasing
- Use the Compiler
- Don't Bother
Reducing Message-Sending
One particular idiom in Cocoa had so much overhead that Apple introduced an entirely new language feature to avoid it. The common way of iterating over a collection in Objective-C looked like this:
NSEnumerator *e= [collection objectEnumerator]; for (id obj = [e nextObject] ; nil != obj ; obj = [e nextObject])
This approach involved creating a new object (the enumerator), which required a call to malloc() and some other work. Getting each value then involved sending a message to the enumerator, which then typically sent a message to the collection. As you can imagine, this wasn't especially fast. Often, it could be slower than just doing something like this:
for (NSUInteger i=0, count=[array count] ; i<count ; i++) { id obj = [array objectAtIndex: i]; }
With Objective-C 2.0, Apple introduced this new syntax:
for (id obj in collection)
The new syntax isn't intrinsically faster, but it's compiled to something that is faster. The for..in loop is implemented as two nested loops. The outer loop sends a message to the collection, with a pointer to an on-stack buffer as one of its arguments. The collection then writes some object pointers into it, or returns a pointer to an internal array if that's how it stores values. The inner loop then iterates over this buffer. You only need one message for each 16 objects that you get from the collection, which is why Apple calls this fast enumeration.
This same pattern can be used for iterating over other things. The most obvious example is strings. You can get each character in a string by sending a -characterAtIndex: message to an NSString instance, but it's a lot faster to use -getCharacters:range:. This method copies a group of characters into a buffer provided by the caller. You can use this technique in the same way that the compiler uses the fast enumeration method, like this:
NSUInteger end = [str length]; NSRange range = { 0, 32 }; while (range.location < end) { unichar buffer[32]; if (range.location + range.length > end) { range.length = end - range.location; } [str getCharacters: buffer range: range]; range.location += 32; for (unsigned iterator=0 ; i<range.length ; iterator++) { unichar c = buffer[iterator]; // Do something with the character } }
This code fetches characters up to 32 at a time, allowing you to iterate quickly over a string. This pattern is so common that I have a macro that wraps it. (I implemented direct support for it in clang a while ago, but the language extension was rejected by Apple.) It's sometimes tempting to use this pattern instead:
const char *cStr = [str UTF8String]; NSUInteger end = [str length]; for (NSUInteger i=0 ; i<end ; i++) { char c = cStr[i]; }
This pattern has two disadvantages. First, you're dealing with UTF-8 instead of UTF-16, so you're far more likely to encounter characters that span two loop iterations. The second, more important disadvantage is the lack of guarantee that the string uses UTF-8 internally. If it doesn't, then it needs to allocate an NSData object to contain the result and then autorelease it. The memory from this operation won't be reclaimed until the end of the current run loop, and you have the overhead of a potentially large memory allocation.