Search code examples
objective-ctypessubclassingmessage-passing

Calling a subclass method on a variable typed as superclass


I have created a class named Foo:

@interface Foo:NSObject{
     int myInt;
}
@property int myInt;
@end

and a subclass of Foo named Bar:

@interface Bar:Foo{
     NSString *myString;
}
@property (copy) NSString *myString;
@end

I am trying to store Bar as a Foo object in an array, like this:

-(void)createBar{
     Foo *object = [[Bar alloc]init];

     // myArray is an instance of NSMutableArray
     [myArray addObject:object];
}

I am doing this because I actually have more than one subclass of Foo (I don't want to list them all). When I grab an object from the array and send the message to the object to get the myString variable, the application doesn't do anything. Example:

-(NSString *)getStringFromFooAtIndex(NSUInteger)index{
     Foo *object = [myArray objectAtIndex:index];

     return [object myString];
}

Am I misunderstanding how the 'message' works? I was under the assumption that I can send a message to an object and it would call it whether it was there or not. Do I need to be doing this some other way? The array will hold all the different types of Foo child classes and I need it to store them there.


Solution

  • I was under the assumption that I can send a message to an object and it would call it whether it was there or not.

    You can indeed send any message to any object; that's part of the fun of Objective-C. The type of the variable (Foo *, Bar *, id, or anything else) has no effect on the message send. The object to which the variable points knows its class. The lookup of the corresponding method is done at runtime, via that class. The compiler turns the bracketed expression into a call to a function, objc_msgSend.

    You should be getting a warning about [object myString] when building, saying "'Foo' may not respond to 'myString'" -- the compiler knows that there's at least one class somewhere that has a method corresponding to myString, and it knows that, at compile-time, Foo doesn't seem to be one of those, but it can't guarantee that the Foo won't be able to do something with the message at runtime. Messages can be resolved in custom ways during runtime. Notice that if you change the type of the variable to id, the warning disappears -- the compiler can no longer reason about what methods are available.

    If it turns out that the object to which you send myString doesn't respond (i.e., if the object really is a Foo instead of a Bar, or one of Foo's subclasses that doesn't implement myString), an exception will be raised. This is the default response (by anything that inherits from NSObject) to an unrecognized message.

    If you have a heterogenous collection of objects to which you need to send messages, you will probably want to test each object first. You can do as jstevenco suggested, and test for functionality:

    if( [object respondsToSelector:@selector(myString)] ){
    

    or test for identity:

    if( [object isKindOfClass:[Bar class]] ){
    

    The latter will pass if the object is a Bar or any of Bar's subclasses. Use isMemberOfClass: to test only for the class you specify.