read

I've seen it time and again in various languages and code bases. 'isKindOfClass:' and its related methods in other Object Oriented languages are always present. However, the need to use these is few and far bewteen. I most often see this method used in a manner similar to this:

- (void) doSomething:(id<NSFastEnumeration>)parameter;
{
    for (id obj in parameter)
    {
        if ([obj isKindOfClass:[NSString class]])
        {
            [self performStringOperationOn:obj];
        }
        else if ([obj isKindOfClass:[NSArray class]])
        {
            [self performArrayOperationOn:obj]
        }
        else if ([obj isKindOfClass:[NSDictionary class]])
        {
            [self performDictionaryOperationOn:obj];
        }
    }
}

While is code works, it certainly presents a logic bottleneck and makes the codebase difficult to adapt to change. The if/else list makes assumptions about your enumeration data structure and make it tedious to add and change your data structure in the future. A common solution to traversing a data structure is to use the Visitor pattern. We can certainly add visit and accept methods to built in data types like strings, dictionaries and arrays using categories. However, there is another subtle use of categories that make a but more concise and clear the intent of the solution.

The main criticism of this style of code is that its far more procedural than object oriented. It's the brute force solution. Let's look at one that, while is more code, feels more Object Oriented.

Looking at what we're doing here is performing a different operation based on the data type. Wouldn't it be nice if we could just tell the data to do the operation, and it would do it? Something along the lines of this:

[obj performOperation];

If obj is a NSString, it'll perform the string operation, if it's an NSArray, it'll perform the array operation, etc. Eventually, we'll end up with a solution with a category for each type we expect in the array, like so:

@interface NSString (Additions)
- (void) srm_performOperation;
@end

@interface NSArray (Additions)
- (void) srm_performOperation;
@end

@interface NSDictionary (Additions)
- (void) srm_performOperation;
@end

...and our traversal operation turns into:

- (void) doSomething:(id<NSFastEnumeration>)parameter;
{
    for (id obj in parameter)
    {
        [obj srm_performOperation];
    }
}

And, if the collecting or traversing object context is needed, a context parameter could always be passed along. Now, this may crash when you have an object that doesn't have a category with the srm_performOperation method, but it will be easy to track down which type is currently not supported, and creating the new category to add this support. One of the other benefits of this approach is that the logic that is relevant to performing this operation are 1) in it's own method, and not hidden in a clump of if/else or switch/case statements 2) the logic is also more closely associated with the class onto which it's operating via the category. That is, the category is a loose enough coupling that it doesn't mean the operation needs to know any private info, but still close enough because the operation only applies to objects of that type.

And, if you have doubts about using this without a call to respondsToSelector: beforehand, it can most certainly be added in there.

As an added bonus, you can also add a category for NSNull to basically ignore NSNulls that aren't normally ignored by simply adding a category like this:

@interface NSNull (Additions)
- (void) srm_performOperation;
@end

@implementation NSNull (Additions)
- (void) srm_performOperation;
{
    //do nothing!
}
@end

I hope this helps get rid of a lot of unnecessary if/else or switch/case statements in your object oriented code bases!

Blog Logo

Saul Mora


Published

Image

Saul Mora

iOS Developer, Engineer, Dad, Human

Back to Overview