2
votes

Any way to fix this?

I changed my a signature of a function to pass NSString object instead of NSNumber object. Except I have some instances that still pass the old NSNumber object. It's hard to track down because the compiler doesn't display ANY errors or warnings for this. I tried deleting the DerivedData folder. Doesn't help.

I also tried to do Analyze, that doesn't catch those problems either.

I know I can do an NSAssert check to make sure that the incoming parameter type is proper, but this seems backwards. This should be something the compiler picks up and warns me.

Any suggestions?

- (void) test:(NSString *)par;

calling

NSString *str = (NSString *)[array objectAtIndex:0];

Except the object is NSNumber, so technically it casts an NSNumber to NSString. Debugger sees it as NSNumber, so not sure why it's being allowed in the first place.

3
Not much you can do about this except learning to code more defensively where needed. When you explicitly cast to something you have to be sure it will work.Georg Fritzsche
Is there no breakpoint I can set to debug these kinds of bad typings?AWF4vk
@David - if you explicitly typecast a pointer like that, it's pretty much game over. How can the compiler tell you didn't mean to do that? It doesn't know what's in the array - you have to maintain that information yourself.Carl Norum
You can set Xcode to break on ObjC exceptions like in this case when an unrecognized selector is being called - that's it. For ObjC at runtime it basically doesn't matter of what type an object is in most cases as long as it supports the selectors being called on it.Georg Fritzsche

3 Answers

2
votes

Any way to fix this?

not easily. assertions (as you mentioned) are a starting point.

I changed my a signature of a function to pass NSString object instead of NSNumber object.

objc doesn't work that way - objc objects just pass addresses through their arguments/variables at execution.

there is no explicit type conversion provided by the language when typecasting; in case that was your expectation.

nor does objc use a typesafe cast to determine whether the argument is of the type it is being cast to.

Except I have some instances that still pass the old NSNumber object. It's hard to track down because the compiler doesn't display ANY errors or warnings for this. I tried deleting the DerivedData folder. Doesn't help.

you get NSNumbers because that is what exists in the array.

I also tried to do Analyze, that doesn't catch those problems either.

it's a very dynamic language, and that is how it was designed.

the analyzer cannot pick up those issues or look for them because the check must be performed at runtime.

also, it's commonplace to pass objc objects around and use them dynamically (using respondsToSelector: and isKindOfClass:).

I know I can do an NSAssert check to make sure that the incoming parameter type is proper, but this seems backwards. This should be something the compiler picks up and warns me.

I use assertions and have written several checks and types to return type safety where i want it.

i know of no public lib for this - you're probably on your own to implement the checks you want. creating a simple header for these checks and implementing them is easy enough.

Except the object is NSNumber, so technically it casts an NSNumber to NSString. Debugger sees it as NSNumber, so not sure why it's being allowed in the first place.

for objc variables, the debugger evaluates the object at the address to determine its type rather than using the type declared by the variable.

1
votes

The fact that the debugger sees it as its correct (NSNumber) type doesn't mean the compiler should also see that. Compilation happens way before debugging, and the only info the compiler has is the static source code. If you're casting a pointer to another type, you're effectively telling the compiler to "forget everything you think you know about this pointer; it's now THIS type". The compiler can't know that there will actually be a different type there at runtime.

The compiler will warn you about conflicting argument types, but in order to see that warning, the types have to actually conflict at compile time. Casting is forcing the types to match at compile time, so your only option is to change or remove all of the offending casts.

0
votes

To further the discussion: Objective-C objects can be treated as typeless. Because the runtime is fully reflective, both [objTypeA someSelector] and [objTypeB someSelector] produce exactly the same compiled code. This is in contrast to languages like C++ where the type of the object needs to be known in order for it to be clear how a particular method is called.

Another way of describing this is that Objective-C supports duck typing. So a solution to your problem would be:

@interface NSString (stringValue)

- (NSString *)stringValue { return self; }

@end

// ... elsewhere ...

- (void)test:(id)par
{
    NSString *stringValueOfPar = [par stringValue];

    // now proceed with stringValueOfPar as you previously did with par
}

What you've done there is widened the definition of your test method so that instead of taking a specific type, it takes any type that will convert itself to a string when you call stringValue. NSNumber already satisfies that condition, but NSString doesn't. So you extend NSString. So what you've created is an informal protocol.

Justin's answer is the more practical and the proper one to use, this one is effectively to deploy Objective-C's abilities to patch up a mistake that you shouldn't be making in the first place. I've posted this answer to to try to help explain why this sort of thing is not just allowed in Objective-C, but sometimes quite useful.