Saturday, July 16, 2011

iOS Pro Tip: Know Thine API!

I was reading through some code at my current project (P.S.: ThoughtWorks is hiring! Contact me if you want to submit a résumé!), when a good old-fashioned C-style for loop caught my eye. You know the kind:
for (int i = 0; i < someArray.length; i++) {
// do something with someArray[i]
}
It turns out that if someArray is an NSArray, there is API defined to help with this:
[someArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// do something with obj
// set stop to true if you want to skip the remaining objects for some reason
}];
Note: Yes, I'm aware that there is also syntax for this in Objective-C 2.0, in the form of fast enumeration. The code I was converting was specifically interested in returning the index of a particular object in the array, which is why I went with this form.

Anyway, further reading in the NSArray API led me to another fun method: indexOfObjectPassingTest. It also takes a block, which returns a boolean value indicating whether or not the current object passes the test. If it does, you get the index back and move on with your life. This turned out to be exactly what I needed to port the original bit of code to full modernity.

I also came across an example of code that was using NSString +stringWithFormat. It would create the original format string, conditionally modify it based on the presence of some optional data, then pass all available data in to format the final string. Something like this:
NSString *formatString = @"required: %@";
if (optionalData) {
formatString = [formatString stringByAppendingString:@", optional: %@"];
}
NSString *result = [NSString stringWithFormat:formatString, requiredData, optionalData];
This seemed potentially error-prone to me; I didn't like the idea of always passing in the (potentially nil) optional data, even if I was convinced there would be no placeholder for it in the format string.

A quick read through the NSString class reference led to the instance method stringByAppendingFormat. This allowed for a workflow more like the following:
NSString *result = [NSString stringWithFormat:@"required: %@", requiredData];
if (optionalData) {
result = [result stringByAppendingFormat:@", and optional:%@", optionalData];
}
This way I did not have to dance around getting the format string just right before populating it; I could tack on the extra bits on the fly, which just seems cleaner.

2 comments:

Stew said...

If you're used to the array capabilities in Ruby, you might want to check out NSArray+Blocks and NSArray+Access in the github project below. I end up using that because 'enumerateObjectsUsingBlock' just seems verbose

https://github.com/kevinoneill/Useful-Bits/tree/master/UsefulBits/Source

David Rupp said...

Very nice, Stew. I can definitely see using these at some point. I normally try to stick to native API as much as possible, because I know it'll always be there. But these categories definitely appeal to my inner Rubyist.