super respondsToSelector: returns true but actually calling super (selector) gives “unrecognized selector sent to instance”

后端 未结 5 1676
不知归路
不知归路 2021-01-01 12:40

OK, I am a little confused.

I have a subclass of UIScrollView, which is my attempt at a horizontally scrolling \"table view\" like UI element. UIScrollView itself s

相关标签:
5条回答
  • 2021-01-01 13:19

    Unless you override responds to selector in your class you will be using the default implementation when you call super which will check the current instance. If you want to see if a instance of a type of object responds to a selector use +(BOOL)instancesRespondToSelector:(SEL)aSelector;

    This will check the object and its parent objects. So in your case you want to the following:

    [UIScrollView instancesRespondToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
    
    0 讨论(0)
  • 2021-01-01 13:25

    When you call

    [super respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
    

    This is going to the superclass and running its implementation of respondsToSelector. This looks at the instance (in this case your custom scroll view) and determines if it responds to that selector or not.

    When you call

    [super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
    

    You try to send a message using the superclass implementation of that method, which in this case does not exist, causing the crash.

    Looks like jjburka got to it first - you need to call

    [UIScrollView instancesRespondToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
    

    Also, it won't crash if you use -[super performSelector:] from unrecognized selector - perform selector gets the instances implementation of the selector. It will crash from infinite recursion.

    0 讨论(0)
  • 2021-01-01 13:34

    [super respondsToSelector:@selector(frobnosticate:)] doesn't do what you think.

    It goes to the superclass, gets the implementation of respondsToSelector: there, and then runs it on the current object. In other words, super represents the same object as self, it just starts the method lookup one step higher in the inheritance tree.

    So you're running respondsToSelector: on this subclass, which replies "YES", but then later trying to get gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: from the superclass, which doesn't have it.

    To check instances of the immediate superclass, you would use instancesRespondToSelector:, as jjburka recommend, but I would suggest [self superclass] as the subject of that, like so:

    [[self superclass] instancesRespondToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)];
    

    which avoids hardcoding class names.

    0 讨论(0)
  • 2021-01-01 13:42

    After looking at the other answers, the best solution is to use [[MapControllerSublcass1 superclass] instancesRespondToSelector:_cmd]. If you use what is recommended above, something like [BaseClass instancesRespondToSelector:_cmd], you run into the problem of changing your class hierarchy and accidentally forgetting to change BaseClass to the new superclass or your subclass.

    [[self superclass] instancesRespondToSelector:...] is incorrect as explained above in the comments and it actually says so on Apple's documentation (See respondsToSelector: in NSObjct). It only works when you have 1 level of subclassing, so it gives you the illusion that it is an actual solution. I fell for it.

    And [[super class] instancesRespondToSelector:...] doesn't work and is the whole point of this SO question.

    For example, I have a BaseMapController that implements some of the methods in MKMapViewDelegate, but it doesn't implement mapView:regionWillChangeAnimated:. MapControllerSublcass1 inherits from BaseMapController. And MapControllerSubclass2 inherits from MapControllerSublcass1.

    In my code I have something like this, and it works fine.

    MapControllerSublcass1.m:

    - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
      if ([[MapControllerSublcass1 superclass] instancesRespondToSelector:_cmd]) {
        [super mapView:mapView regionWillChangeAnimated:animated];
      }
    }
    

    MapControllerSubclass2.m:

    - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
      if ([[MapControllerSubclass2 superclass] instancesRespondToSelector:_cmd]) {
        [super mapView:mapView regionWillChangeAnimated:animated];
      }
    }
    
    0 讨论(0)
  • 2021-01-01 13:43

    As a summary for someone with the same case, there are two problems in the original question:

    1. Checking whether the superclass responds to that selector, which as suggested by @jjburka is best done by using instancesRespondToSelector:.
    2. Actually invoking the method on the superclass without the compiler complaining even though it's declared in a private header. This can be neatly achieved by redeclaring it in a category (see this question).

    Putting it together this gives:

    // … In subclass implementation file
    @interface UIScrollView () <UIGestureRecognizerDelegate> @end
    
    // … In gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
    if ([UIScrollView instancesRespondToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
        return [super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
    }
    
    0 讨论(0)
提交回复
热议问题