views:

1386

answers:

3

I'm learning ObjectiveC and ran into a problem relating to introspection. Basically, I'm looping through an array of objects and determining if they accept the lowercaseString selector. If they do, I call that selector on the object. After I ensure that the object responds to that selector, I call it. However, when I do, I get this warning: "warning: 'NSObject; may not respond to '-lowercaseString'"

Although the code works fine as written, I'd like to not get the warning. I'm assuming that there's a "right" way to make sure that I don't get that warning (i.e. without just turning warnings off). Any ideas?

NSMutableArray *myArray = [[NSMutableArray alloc] init];

[myArray addObject:@"Hello!"];
[myArray addObject:[NSURL URLWithString:@"http://apple.com"]];
[myArray addObject:[NSProcessInfo processInfo]];
[myArray addObject:[NSDictionary dictionary]];

SEL lowercaseSelector = @selector(lowercaseString);

for (NSObject *element in myArray) {
    if ([element respondsToSelector:lowercaseSelector]) {
        NSLog([element lowercaseString]); // Warning here
    }
}
+4  A: 

Hey Tyson,

Just cast your NSObject to an NSString before calling the function:

for (NSObject *element in myArray) {
    if ([element respondsToSelector:lowercaseSelector]) {
        NSLog([(NSString*)element lowercaseString]); // No warning!
    }
}
Ben Gotow
But what if the object doesn't support all the selectors that NSString supports? My OOP-sense tells me that that would be bad. I'm just checking for one selector, regardless of what superclass it belongs to.
Tyson
Well, you're assuming that -lowercaseString returns an NSString, since that's what NSLog expects for the format string. If, for whatever reason, it doesn't return an NSString, then you're passing something other than an NSString as the format string, and NSLog will choke on it.
Peter Hosey
The safest way would be to get the `lowercaseString`, store it in a variable, and test whether it's a kind of NSString. You probably don't need to be that paranoid, though, and if you're not going to be, you might as well declare the variable as, or cast the return value to, `NSString *`.
Peter Hosey
+10  A: 

You can also use id which is any type of object.

for (id element in myArray) {
    if ([element respondsToSelector:lowercaseSelector]) {
        NSLog([element lowercaseString]);
    }
}
FigBug
I figured NSObject would be the proper one, but, indeed, id seems a better way of doing it.
Tyson
as long as you don't need to call retain or release on element ;-)
Ben Gotow
what's wrong with calling retai/release on an id?
FigBug
If you have an object that conforms to a protocol and you use id<myProtocol> as the data type for it, it complains that retain/release (and other NSObject functions) aren't found in the protocol. It just assumes the protocol will define every method called on the object. Works fine otherwise - that particular situation drives me crazy, though!
Ben Gotow
The workaround to this is to make all your protocols conform to <NSObject (which is a protocol as well as a class). Anyway, this doesn’t apply to plain id.
Ahruman
+2  A: 

How about using performSelector:?

SEL lowercaseSelector = @selector(lowercaseString);

for (NSObject *element in myArray) {
    if ([element respondsToSelector:lowercaseSelector]) {
        NSLog([element performSelector:lowercaseSelector]); // No warning
    }
}

This would get rid of your compiler warning.

szzsolt
Ah, this seems like a nice way to do it. Thanks for the response.
Tyson