Search code examples
objective-ccocoansarraysubclassing

In Objective-C how to deal with NSArray of related objects


Animal is a class with a BOOL property called alive. Monkey, Zebra and Walrus are sub-classes of Animal. If I have an instance of an NSArray called zoo containing a mix of instances of Monkey, Zebra and Walrus, and I'd like to find the first alive Zebra instance, I might do something like this:

Zebra *zebra; 
for (Animal *animal in zoo) {
    if ([animal isMemberOfClass:[Zebra class]] && animal.alive) {
        zebra = animal;
        break;
    }
}

Problem is the compiler complains about incompatible pointer types when I set zebra = animal. If I do some casting like zebra = (Zebra *)animal then it seems to work, but I'm not sure that kind of casting is safe in Objective-C.

What is a better way of dealing with situations like this?


Solution

  • You're experiencing an "is a" problem.

    If you had been assigning zebra to animal, there would have been no issue, since the class of zebra is Zebra, which "is a" Animal.

    But with what you're doing, you're assigning animal to zebra, but animal is of class Animal, the superclass. The "is a" test fails, but a simple casting:

    zebra = (Zebra *)animal;
    

    takes care of the compiler warning. And yes, it's safe.

    Here's a more modern way of doing the same thing. Incidentally, since, in the last line, -objectAtIndex: returns id, the aforementioned problem doesn't arise :

    NSUInteger index = [zoo indexOfObjectPassingTest:^BOOL(Animal *animal, NSUInteger idx, BOOL *stop) {
        return ([animal isMemberOfClass:[Zebra class]] && animal.alive);
    }];
    Zebra *zebra = (index != NSNotFound) ? [zoo objectAtIndex:index] : nil;
    

    Note that I did tweak one of the arguments to the block from id obj to Animal *animal; if you know your collection contains only references to instances of Animal, you certainly can do this.