Is it possible to cast an object in Objective-C so as to tell the compiler that its type could be one of many?
For example, in my answer to iOS: Two Gestures, One Target-Action, I know an object will either be a UITapGestureRecognizer or a UILongPressGestureRecognizer but am not sure which one. And, both of those classes respond to numberOfTapsRequired but not through a common protocol. They just both implement the same property.
So, to get around compiler errors, I just cast the object as UILongPressGestureRecognizer. This works now, but if a future version of the iOS SDK changes the name of the UITapGestureRecognizer numberOfTapsRequired property (and left that of UILongPressGestureRecognizer unchanged), then my code would compile but crash with an unrecognized selector exception on a double-tap.
So, if there were a way I could tell the compiler, "Hey, I know this object is either one of two types," then that would allow me to make an accurate cast.
If you can't do this in Objective-C, do any programming languages allow this? I hear C# pretty much lets you do anything.
I don't think that a "multiple cast" exists in Objective-C, but you could use something like this to catch this issue at compile-time.
if([gestureRecognizer isKindofClass: [UITapGestureRecognizer class]]) {
(UITapGestureRecognizer*)gestureRecognizer.numberOfTapsRequired;
}
else if([gestureRecognizer isKindofClass: [UILongPressGestureRecognizer class]]) {
(UILongPressGestureRecognizer*)gestureRecognizer.numberOfTapsRequired;
}
Related
My NSDocument subclass implements selectAll:. Only problem is, I'm using NSTableView, and it also implements selectAll:. However, the selectAll: action in NSTableView doesn't do what I want, and it does prevent the selectAll: method in my Document class from ever being reached in the responder chain.
I already have a subclass of NSTableView, and after poking around a bit I got things working the way I want by adding a respondsToSelector: method to my NSTableView subclass which lies to the runtime by telling it there is no selectAll: action:
-(BOOL)respondsToSelector:(SEL)targetSelector
{
if (targetSelector == #selector(selectAll:)) {
return FALSE; // we don't want tableView's implementation of selectAll
}
return [super respondsToSelector:targetSelector];
}
This seems to work fine, allowing the selectAll: method in my document subclass to do its thing. But this solution leaves me a bit uneasy. What about other action methods I have implemented in this subclass? Do I need to manually check and return true for each of them? I do have two actions defined in this subclass, moveLeft: and moveRight:, and they seem to work, even though I am not handling them in respondsToSelector:. So my question is, am I doing this correctly, or is there something I am missing? Or perhaps there is some entirely different way to do this properly?
By the way, I got the idea of overriding respondsToSelector from this post on the OmniGroup forum:
http://mac-os-x.10953.n7.nabble.com/Removing-an-action-from-a-subclass-td27045.html
Sending a message to super affects which implementation of that method we use. It doesn't change who self is.
So let's try to imagine how respondsToSelector: works. Given a selector mySelector, it probably introspects every class up the superclass chain, starting with [self class], to see whether it actually implements mySelector.
Now then, let's say your subclass is called MyTableView. When MyTableView says
[super respondsToSelector:targetSelector]
what happens? The runtime will look up the superclass chain for another implementation of respondsToSelector:, and eventually will find NSObject's original implementation. What does that implementation do? Well, we just answered that: it starts the search for an implementation of targetSelector in [self class]. That's still the MyTableView class! So if you have defined moveLeft: in MyTableView, respondsToSelector: will find it and will return YES for moveLeft:, exactly as you hope and expect.
Thus, to generalize, the only selector for which this search has been perverted is the search for selectAll: - exactly as you hope and expect. So I think you can relax and believe that what you're doing is not only acceptable and workable but the normal solution to the problem you originally posed.
You might also like to look at the Message Forwarding chapter of Apple's Objective-C Runtime Programming Guide.
This is from a book on iphone game development.
[((GameState*)viewController.view) Update];
"viewController" is an instance of UIViewcontroller, "GameState" is a subclass of UIView, and "Update" is a method of "GameState". Can you please tell me what is happening. Does this syntax allow the viewController to use the methods of GameState? I apologize if this is a stupid question.
All that's doing is telling the compiler "hey, viewController.view is actually of type GameState*". It doesn't actually do anything to it though, just lets the compiler know so it won't warn about it.
Note that it's entirely legal to lie to the compiler like this, and it will believe you, and not check your work, so it's best to avoid casting if you can. If you cast it to something it isn't, it will crash if you try to use methods it doesn't have.
What's going on here is a C type cast: you are telling the compiler that you know that your viewController's view is of type GameState, and that you know that it's OK to invoke methods of GameState here, even though these methods are not part of the UIView's interface.
Means that viewController's view is casted to a GameState (subclass of UIView) and in this way the compiler does not complain that Update method is invoked.
This has the inconvenience of potentially generating a runtime error so to be safe I will enclose the previous statement in:
if ([viewController.view isKindOfClass:[GameState class]])
Recently turning to iOS after having worked with Cocoa, I was startled to get a SIGABRT with the following error: “-[UIDeviceRGBColor copyWithZone:]: unrecognized selector sent to instance…” I had called “copy” on a UIColor.
I looked at the class references and, zounds, UIColor does not adopt any protocols, in contrast to NSColor.
Now, this is not a big deal. I was just attempting to be more efficient by taking active ownership of a color instance so as to discard it immediately after use. But I thought the purpose behind Apple’s omitting a garbage collector in iOS was to encourage developers to do exactly what I was doing, to keep a lean memory profile on the memory-starved, battery-challenged portable devices.
Any ideas on Apple’s rationale, or is there some error in my assumptions?
I don't understand why you think implementing the NSCopying protocol would "encourage active memory management".
Since UIColor is immutable (it implements no methods that change its internal state), there is no point making a copy. Just retain it if you want to keep it around, and release it when you're done. There is no need for anything else.
If you really wanted, you could add copying in a category:
#implementation UIColor (Copying) <NSCopying>
- (id)copyWithZone:(NSZone *)zone
{
return [self retain];
}
#end
But obviously that doesn't actually give you any new functionality. Apparently Apple didn't think it was worth the time when they implemented that class.
My app needs to work on both iOS5 (UIColor>>#copyWithZone doesn't exist) and iOS6+ (UIColor>>#copyWithZone exists) so I came up with the following:
#implementation UIColor(ios5CopyWithZone)
+ (void)initialize
{
// iOS5 dosn't include UIColor>>#copyWithZone so add it with class_addMethod.
// For iOS6+ class_addMethod fails as UIColor>>#copyWithZone already exists.
Class klass = [UIColor class];
Method methodToInstall = class_getInstanceMethod(klass, #selector(ios5CopyWithZone:));
class_addMethod(klass, #selector(copyWithZone:), method_getImplementation(methodToInstall), method_getTypeEncoding(methodToInstall));
}
// UIImage is immutable so can just return self.
// #retain to ensure we follow mem-management conventions
-(id)ios5CopyWithZone:(NSZone *)__unused zone
{
return [self retain];
}
#end
The code attempts to adds UIColor>>#copyWithZone using the runtime's class_addMethod. I don't know if this is any better than implementing UIColor>>#copyWithZone directly in a category, however reading Apple's Avoid Category Method Name Clashes implies that it is bad practice to reimplement an existing framework method (that is UIColor>>#copyWithZone in iOS6). However I realise that +initialize could potentially trample on a framework's +initialize.
I've just come across some code in Three20 that looks like this:
SEL sel = #selector(textField:didAddCellAtIndex:);
if ([self.delegate respondsToSelector:sel]) {
[self.delegate performSelector:sel withObject:self withObject:(id)_cellViews.count-1];
}
On LLVM 2.0, this causes the compilation error:
error: arithmetic on pointer to interface 'id', which is not a constant size in non-fragile ABI
I know why that error is occurring and I know how to fix it. I just need to invoke the method directly, like so:
SEL sel = #selector(textField:didAddCellAtIndex:);
if ([self.delegate respondsToSelector:sel]) {
[self.delegate textField:self didAddCellAtIndex:(_cellViews.count - 1)];
}
My question is, if you know both the selector and its arguments at compile time, why would you need to use performSelector:withObject:withObject: at runtime? I don't see why the code was written this way in the first place. If the selector and arguments were dynamically passed into the method, I may understand, but they're not, the selector and its arguments are hard coded, (even if the index does change during run time, its method of obtaining the index is hard coded.)
If someone could explain to me a good reason why this would be necessary, I'd be grateful. Otherwise, I'll be over here changing all this code.
After a little more digging, it looks like the TTPickerTextField class that this code is found in is an indirect subclass of a UITextField.
As such, it is piggy-backing on UITextFields delegate property, which doesn't conform to the TTPickerTextFieldDelegate protocol where the method textField:didAddCellAtIndex: is declared.
I have come to the conclusion that this code is just laziness. No reason why the UITextFields delegate property had to be piggy-backed, making this confusing, error prone code necessary.
My own approach would have been to leave UITextFields delegate property alone, and add my own property in my specific subclass that handled the specific delegate methods.
Just to clarify - the 'solution' I mentioned in the question fixes the compiler error, but generates a warning that the method can't be found and will be assumed to return id. This is what the original code was 'solving' but that only worked in GCC. No longer with LLVM 2.0.
Last edit, I promise:
My final solution to combat this laziness and get rid of the warning and error is an ugly hack:
[(id <TTPickerTextFieldDelegate>)self.delegate textField:self didAddCellAtIndex:(_cellViews.count - 1)];
Cast UITextFields delegate to an id that conforms to TTPickerTextFieldDelegate and then invoke the method directly.
Please don't be lazy :(
That respondsToSelector/performSelector combo is an idiom for optional delegate methods. The delegate isn't guaranteed to have that method defined, so a direct call to it would cause a compiler warning.
What the compiler was actually complaining about in this case:
[self.delegate performSelector:sel withObject:self withObject:(id)_cellViews.count-1];
error: arithmetic on pointer to interface 'id', which is not a constant size in non-fragile ABI
is risky pointer arithmetic... 'id' is a pointer type, so:
(id)_cellViews.count-1
tells the compiler it's going to subtract one from a pointer instead of an integer....which is probably not the intent of that code. The withObject argument of performSelector has to be a pointer, it can't be a primitive. You can get around this by wrapping _cellViews.count - 1 in an NSNumber, and unwrapping it in the delegate method.
[self.delegate performSelector:sel withObject:self withObject:[NSNumber numberWithInt:_cellViews.count-1]];
I am working on a project where I have a class which has UIView property. I also define a class which is a subclass of UIView which defines a certain method. If I have the following code, I get a warning when I build:
// In this example, myView is UIView property which *may* contain a UIView or
// my subclassed-UIView which has the myMethod method
if([myView respondsToSelector:#selector(myMethod)]){
[myView myMethod]
}
The warning is "UIView may not respond to '-myMethod'". The warning obviously doesn't stop the app from being built, but I am just trying to figure out how to deal with it. Is this the correct way to do this? Is there a way to stop this warning?
The warning is only because the compiler doesn't know if that view is your custom subclass. Of course, at runtime it will work fine, since it will be a subclass. You have two options to fix it:
[myView performSelector:#selector(myMethod)];
(So the compiler doesn't check the method call at all)
Or, better:
[(MyViewClass *)myView myMethod];
That way the compiler acts as if the object really is your view subclass (after you performing the check of course).
For that matter, it might make sense to check for your class rather than the method:
if ([myView isKindOfClass:[MyViewClass class]]) { ...
You can use:
[myView performSelector:#selector(myMethod)];
This is a static typing warning, telling you that the type the variable is declared as does not respond to that selector. Since you're actually using a subclass that you've confirmed responds to the selector, you know this isn't a problem, but the compiler isn't smart enough to figure this out. There are a few ways you can fix this. In decreasing order of safety:
Cast the variable to what it actually is that does respond to the selector, either a specific class or a protocol. You'll still need to import the appropriate header or the compiler will suspect you mistyped something. Which option is best depends on your situation (e.g. whether there's one "correct" class to cast to).
[(id<SomeProtocolWiththatSelector>)myView myMethod];
[(SomeUIViewSubclass *)myView myMethod];
Cast the variable to id to disable static typechecking. You'll still need to import a header with the declaration so the compiler knows some method exists or it will still give the "I'm not sure if this is a real method" warning.
[(id)myView myMethod];
Use performSelector:. This will not do any checks at compile-time, so you don't need to import any headers besides Foundation, but the compiler won't catch any typos either, so any mistakes you make mean the program goes boom at runtime.
[myView performSelector:#selector(myMethod)];