3

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
Andrew Riebe
  • 411
  • 7
  • 17

3 Answers3

3

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • It appears that my problem isn't with the Array, or the message, but that I am inserting 'Bar' into an NSArrayController (let's call it arrayController). If I initialize Bar normally and add it to 'myArray' in code, I can send messages to it all day long and it will work. If I use `[arrayController add:bar];` I loose anything associated with Bar and only get the properties and methods for Foo. Is this normal? Thanks – Andrew Riebe Dec 13 '11 at 19:43
  • I don't understand what you mean by "lose anything associated". Can you be specific about what the results of a message send are? Are you talking about code completion in Xcode? Don't mistake that for the actuality of what happens -- the analyzer knows about as much as the compiler, possibly less. – jscs Dec 13 '11 at 19:47
  • Well, let me see if I can explain it better. I have an NSArrayController (arrayController) placed in .xib file. This is linked to the 'App Delegate' object's myArray (the NSMutableArray I am using). arrayController has it's 'Mode' set to Class and it's 'Class Name' set to Foo. If I add a Bar object to it, it takes it as it's a subclass of Foo, but it appears that it is dropping all properties from any object I place in there that isn't defined in the Foo.h implementation file. Is that more clear. I apologize if you can't understand what I am trying to say...still kind of new, lol. – Andrew Riebe Dec 13 '11 at 19:57
  • What are the results that make you think that properties are being "dropped"? The array controller's 'Class name' being set to `Foo` just affects the type of a _new_ object, _created by_ the array controller itself. It _can't_ change the class of objects that you insert into the array. – jscs Dec 13 '11 at 20:14
  • First, if I use bindings to bind a NSTextField to the 'Array Controller's' selection and tell it to use the 'myString' value, it raises an exception. Second, when I use glue code (I think that's what it's called...) when I pull the item out of the array like this: `Foo *object = [myArray objectAtIndex:0]` it doesn't have the property. I checked using a breakpoint to see what the 'object' has for properties and I can't find myString, even though I have defined it as a 'Bar' object when creating it. – Andrew Riebe Dec 14 '11 at 13:22
1

See here for a good summary of how messaging logic is handled. Ultimately an error will be generated if you don't override -(void)doesNotRecognizeSelector:(SEL)aSelector.

If you are going to have a polymorphic collection of objects, you should probably test the object with respondsToSelector: first then either call the method directly or using one of the performSelector: variants according to your needs. Another possibility would be to include an implementation of myString in the base class that does nothing.

Community
  • 1
  • 1
jstevenco
  • 2,913
  • 2
  • 25
  • 36
0

I figured out what the problem was.

In my AppDelegate.m file I have the following method:

- (void)createFoo{
     Foo *foo = [[Bar alloc]init];

     [myArrayController add:foo];
}

This was inserting the 'foo' object into my array controller. I also have this method defined:

- (void) insertObject:(Foo *)object intoMyArrayControllerAtIndex:(NSUInteger)index{
     // handles the inserting of the object as well as working with the undo manager
}

Now, calling [myArrayController add:foo] is what was causing the problem. If I replace that line with [self insertObect:foo intoMyArrayControllerAtIndex:0] then everything works perfectly.

Thank you so much and sorry for wasting your time.

Andrew Riebe
  • 411
  • 7
  • 17