7

I sometimes like to organize IB elements into NSArrays primarily to help me organize my elements. Most often, different classes of objects make it into the same array with each other. While this is a convenient way of organization, I can't seem to wrap my head around why if I have an array like this:

NSArray *array = [NSArray arrayWithObjects:((UITextField *)textField), ((UISegmentedController *)segmentedController), nil];

Why I get "Does not respond to selector" messages when I put a for loop like this:

for (UITextField *text in array) {
    [text setText:@""];
}

The for loop seems to be passed objects that are not of class UITextField.

What is the point of declaring the object's class if all objects in the specified array are passed through the loop?

EDIT Just for reference, this is how I'm handling it as of now:

for (id *object in array) {
    if ([object isMemberOfClass:[UITextField class]]) {
        foo();
    } else if ([object isMemberOfClass:[UISegmentedController class]) {
        bar();
    }
 }
esqew
  • 42,425
  • 27
  • 92
  • 132

3 Answers3

10

When you do

for (UITextField *text in...

the object pointers from the array are cast to UITextField* type - so if the object isn't actually a UITextField, all sorts of weird things may happen if you try to call UITextField methods.

So instead use the id type (no * needed, btw):

for (id obj in array)

Then check the type as you do and call the appropriate methods. Or, filter the array to get only objects of a certain type, then go though that type only:

for (UITextField* text in [array filteredArrayUsingPredicate:...])

Edit: here's how to build class filter predicates:

Is it possible to filter an NSArray by class?

Community
  • 1
  • 1
SVD
  • 4,743
  • 2
  • 26
  • 38
  • Objects aren't cast to new types -- pointers are. The type of the pointer doesn't matter one whit when it comes to dispatching messages. You could send `-appendString:` to an object using a pointer of type `NSWindow*` if you want to, and it'll work just fine as long as the pointer really points to an instance of NSMutableString. (You might get a compiler warning, but it'll work.) If the pointer points to a window, though, you will of course get an error at runtime unless you take steps to prevent that. – Caleb Aug 17 '11 at 22:08
  • You are correct of course, I've modified my answer accordingly. – SVD Aug 17 '11 at 22:12
  • the part that needs editing is "if the object isn't actually a UITextField, all sorts of weird things may happen...". If the object isn't actually a UITextField, you'll get an unimplemented selector exception. – Caleb Aug 17 '11 at 22:15
  • "object doesn't recognize selector". However, if there _is_ a method with the same name (like setText: - fairly generic), it will actually work in the sense that there will be no exception but may not do quite what was expected. That's why I lumped it in "weird things". – SVD Aug 17 '11 at 22:22
  • I find it convenient to use a specific type (like UITextField*) rather than id because then the compiler will check my method calls inside that loop. Of course, one must check to make sure it really is an object of the right class before invoking any of its methods. – progrmr Aug 24 '11 at 17:45
5

What is the point of declaring the object's class if all objects in the specified array are passed through the loop?

The class name is just there to let the compiler know what it should expect to find. This allows it to try to figure out what methods it should expect you to call and how you might treat the object. It's the same idea as passing in an int to a method that takes float. The method will not ignore ints - it's assuming you're passing the correct type. You're just giving this construct a little more credit than it's due:

for (UITextField *text in array)

It just doesn't have that functionality. How you're handling it now is the correct way.

Luke
  • 7,110
  • 6
  • 45
  • 74
0

Are you sure you don't get an error when you run that code? The "does not respond to selector" message is a runtime error, not a compile time error. The compiler has no idea whether the objects in the array implement -setText:, but you should certainly get an error when you actually send that message to an instance of UISegmentedControl.

Another possibility is that you've got a class called UISegmentedController that does have a -setText: method. The name of the class that implements the multi-part bar-graph-looking user interface widget is UISegmentedControl. So either the code you're showing isn't real, tested code, or you've got a class that we don't know about.

Caleb
  • 124,013
  • 19
  • 183
  • 272