There are significant advantages to inheritance in some cases, and isA() is a valid thing to do, if the hierarchy is well-specified. It’s also a perfectly valid choice to accept the limitations of isA() in an imperfect hierarchy if it simplifies the code and makes it easier to understand, it’s all about where the complexity lies in the model.
xc provides isA() via casting:
Dog *fido = (Dog*) animal; // runtime-checked downcast.
// On a mismatch the program traps.
Dog *fido = (Dog* ?) animal; // failable downcast.
// On a mismatch it yields (Dog*)0;
// on success, the retyped pointer.
// Pair it with a subsequent ‘if (fido != nil)’ guard.