views:

1491

answers:

5

I'm using a framework which defines and uses 'ClassA', a subclass of NSObject. I would like to add some variables and functionality so naturally I created 'ClassB', a subclass of 'ClassA'

Now my problem is this. Many of the methods within this framework return instances of 'ClassA' which I would like to cast to my subclass.

For example take this method:

- (ClassA *)doSomethingCool:(int)howCool

Now in my code I try this:

ClassB * objB;
objB = (ClassB *)doSomethingCool(10); 

NSLog(@"objB className = %@", [objB className]);

This runs just fine. No compile or runtime errors or anything. But what is really odd to me is the output:

>> "objB className = ClassA"

The casting obviously failed. Not sure what's happened at this point... objB is typed as 'ClassB', but it's className is 'ClassA' and it won't respond to any 'ClassB' methods.

Not sure how this is possible... Anyone know what I am doing wrong here?

I found a similar post which is exact opposite of what I'm asking here

+3  A: 

You can't just cast a super class to it's subclass. It doesn't actually implement any of your added variables/properties or methods. As soon as you try to message it with a method you define on your subclass, you are going to get a runtime exception and your application is going to quit. You can only safely cast in one direction: from more specific to more general. I.e., you can cast our ClassB to a ClassA safely, using only the methods and properties of ClassA, but not the other way around.

Think of it this way: you have a Car (the parent class) and a FourDoorSedan (the subclass). Every car has an engine and two doors. Now, let's say you are getting a car from somewhere, and that's really all you know about it. You tell the operator, that car is a FourDoorSedan, but in fact it turns out it's not. So when the operator does something like: openBackPassengerDoor, what happens? There are only two doors!! It's the same here.

If you just want to add a little functionality to ClassA, check out Objective-C Categories, they're probably what you want and no casting will be required. Read the documentation carefully, however, because they are not without their caveats.

Jason Coco
You can cast if `doSomethingCool:` really _did_ return an object of type `ClassB`. Its return type is `ClassA`, but it can actually return an object of `ClassB` (since `ClassB` "is-a" `ClassA`). If you _know_ `doSomethingCool:` returned an object of type `ClassB` (which you can check for using `[objB isKindOfClass:[ClassB class]]` if you're not sure), then it's safe to cast and send a message to `objB` to tell it to perform a `ClassB` method. You can also use `respondsToSelector` to be _really_ sure.
mipadi
Although, re-reading the question, if the framework is a third-party framework that doesn't know anything at all about `ClassB` (and hasn't been modified by nick) then it won't be returning objects that really are instances of `ClassB` anyway.
mipadi
Exactly, it's a 3-rd party framework so it's definitely not returning CLassB. Also, we know *for sure* that it is not returning ClassB since [objB class] is returning ClassA.
Jason Coco
I've used categories before, and they would help. The only problem is I'd like to add some additional ivars as well and as far as I know categories only allow you to "mix-in" methods. I guess now I'm curious... is the answer to [this thread](http://stackoverflow.com/questions/988842/is-it-possible-to-downgrade-an-object-in-objective-c-to-its-superclass) wrong? Because it seems to contradict your answer here when you said "You can only safely cast in one direction: from more specific to more general."
nick
It doesn't contradict what I said, he specifically doesn't want to cast. He actually wants to convert the class into a subclass, and the answer was: you should not do that. Casting down works just fine and is a staple in object oriented design.
Jason Coco
I see what you're saying. Didn't read that carefully enough.
nick
A: 

What happens when you call one of your subclass methods on the returned object?

ClassB * objB;
objB = (ClassB *)doSomethingCool(10);
[objB subClassMethod];

Obj-C is pretty free and lose with types, you don't even need to know the type before hand, eg. you could change all of your ClassB references to id. I know it's safer to know you have the expected type but if your code is correct it shouldn't matter.

Henry
His code isn't correct. He's getting a ClassA and just pretending it's a ClassB because he prefers ClassB. That's why you can't arbitrarily upcast something. In your example, his application will crash on line 3.
Jason Coco
I assumed he had passed his ClassB in somewhere and the framework was passing it back to him in which case the code would be fine. If this isn't the case then the question doesn't make any sense. You can't magically turn one class into another. Categories wont work either.
Henry
I think he's just confused about casting, based on what he wrote, I don't think he passed anything in. If he had, he would have gotten that back, not an actual instance of ClassA. However, he wanted to "add some functionality" to ClassA, which is exactly what categories are for.
Jason Coco
+2  A: 

Casting object variables in Objective-C is usually a mistake (there are a few cases where it's right, but never for this sort of thing). Notice that you aren't casting an object — you're casting a pointer to an object. You then have a pointer of type ClassB*, but it still points to the same instance of ClassA. The thing pointed to hasn't changed at all.

If you really want to convert instances of ClassA to ClassB, you'll need to write a ClassB constructor method that can create a ClassB instance from a ClassA. If you really need to add instance variables, this might be your best choice.

As Jason said, though, it's often a good idea to try a category first.

Chuck
Remember—that instance of `ClassA` returned by the method may _really_ be an object of type `ClassB`, which _will_ respond to messages understood by `ClassB` objects.
mipadi
Although now that I re-read the question, not exactly in this scenario, since nick seems to be using a third-party framework that doesn't know about `ClassB` anyway.
mipadi
Also, regardless of what it is, it's a programming error to assume it's anything other than what it's typed to be. The only real exception to this are APIs where base types (like id or NSObject) are used specifically because the caller is meant to know exactly what type the object will be (or is responsible to figure it out programatically) -- like in collections and action messages.
Jason Coco
I think this solution makes the most sense for me. One more question I have is, why didn't I get an RTE when I pointed objB to an instance of ClassA, when objB was specifically typed as a pointer to a 'ClassB'.
nick
You didn't get a warning because *you cast it*. The compiler will warn you when you upcast without an implicit cast. When you specifically tell the compiler that this object *is* ClassB the compiler assumes you know what you're doing and stays silent. Also, if you're going to go with this solution, you should make a container class specifically to associate your new properties/functionality with an instance of ClassA, not a subclass of ClassA.
Jason Coco
A: 

If you just want to add a method to existing objects, subclassing is not the correct way. You can add method to existing classes (and their instances) using a language feature called category.

Example:

//"ClassA+doSomethingCool.h"
@interface ClassA (doSomethingCool)
- (ClassA *)doSomethingCool:(int)howCool;
@end


//"ClassA+doSomethingCool.m"
@implementation ClassA (doSomethingCool)
- (ClassA *)doSomethingCool:(int)howCool
{

}
@end
A: 

Casting doesn't convert from one type of object to another. You can't force some library to change type of object and create another type of objects unless it utilizes some factory pattern.

Dmitriy