Super отвечаетToSelector: возвращает true, но на самом деле вызов super (selector) дает "непризнанный селектор, отправленный в экземпляр",

Хорошо, я немного смущен.

У меня есть подкласс UIScrollView, который является моей попыткой горизонтального прокручивания "табличного представления", такого как элемент пользовательского интерфейса. Сам UIScrollView настраивает UIGestureRecognizers, который он использует внутри себя, и, похоже, он выступает в роли делегата для этих UIGestureRecognizers. У меня также есть моя собственная настройка UIGestureRecognizer для моих горизонтальных элементов/ячеек таблицы и мой собственный набор классов в качестве делегата для моего собственного UIGestureRecognizer. Поскольку мой класс является подклассом UIScrollView, во время выполнения вызовы делегатов UIGestureRecognizer поступают в мой класс для встроенных UIGestureRecognizers UIScrollView и моих собственных UIGestureRecognizers. Немного о PITA, но мы можем обойти это, передавая те, которые нам не нужны.

-(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 
{ 
   if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]])
      return NO; 
      else
      {  
        if ([super respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)])
           return [super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
           else 
           return NO;
        }
}

Проблема в том, что проверка [super respondsToSelector:@selector()] возвращает YES, но когда я на самом деле называю ее return [super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];, я получаю следующее исключение

2012-08-31 12: 02: 06.156 MyApp [35875: 707] - [MyAppHorizontalImageScroller gestureRecognizer: shouldRecognizeSimultaneousWithGestureRecognizer:]: непризнанный селектор, отправленный в экземпляр 0x21dd50

Я бы подумал, что он должен показать

- [UIScrollView gestureRecognizer: shouldRecognizeSimultaneousWithGestureRecognizer:]

Но это может быть ОК. Но проблема в том, что он говорит, что он отвечает, а затем нет.

Другие два подпрограммы делегатов UIGestureRecognizer работают с этим кодом (очевидно, разные селекторы).

Спасибо за понимание.

Ответы

Ответ 1

Если вы не отменяете ответ на селектор в своем классе, вы будете использовать реализацию по умолчанию, когда вы вызываете супер, который будет проверять текущий экземпляр. Если вы хотите увидеть, отвечает ли экземпляр типа объекта на селектор +(BOOL)instancesRespondToSelector:(SEL)aSelector;

Это проверит объект и его родительские объекты. Поэтому в вашем случае вы хотите следующее:

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

Ответ 2

[super respondsToSelector:@selector(frobnosticate:)] не делает того, что вы думаете.

Он переходит в суперкласс, получает там реализацию respondsToSelector:, а затем запускает его на текущем объекте. Другими словами, super представляет тот же объект, что и self, он просто запускает поиск метода на один шаг выше в дереве наследования.

Итак, вы используете respondsToSelector: в этом подклассе, который отвечает "YES", но затем пытается получить gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: из суперкласса, который его не имеет.

Чтобы проверить экземпляры непосредственного суперкласса, вы должны использовать instancesRespondToSelector:, как рекомендует jjburka, но я бы предложил [self superclass] как предмет этого, например:

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

который позволяет избежать имен классов жесткого кодирования.

Ответ 3

Посмотрев на другие ответы, лучшим решением будет использование [[MapControllerSublcass1 superclass] instancesRespondToSelector:_cmd]. Если вы используете то, что рекомендуется выше, что-то вроде [BaseClass instancesRespondToSelector:_cmd], вы сталкиваетесь с проблемой изменения иерархии классов и случайно забываете изменить BaseClass на новый суперкласс или ваш подкласс.

[[self superclass] instancesRespondToSelector:...] неверно, как объяснялось выше в комментариях, и на самом деле так говорится Документация Apple (см. responsesToSelector: в NSObjct). Он работает только тогда, когда у вас есть 1 уровень подкласса, поэтому он дает вам иллюзию, что это фактическое решение. Я влюбился в это.

И [[super class] instancesRespondToSelector:...] не работает и является целым пунктом этого вопроса SO.

Например, у меня есть BaseMapController, который реализует некоторые из методов в MKMapViewDelegate, но не реализует mapView:regionWillChangeAnimated:. MapControllerSublcass1 наследуется от BaseMapController. И MapControllerSubclass2 наследует от MapControllerSublcass1.

В моем коде у меня есть что-то вроде этого, и он отлично работает.

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];
  }
}

Ответ 4

Когда вы вызываете

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

Это переход к суперклассу и выполнение его реализации respondsToSelector. Это рассмотрит экземпляр (в данном случае ваш пользовательский просмотр прокрутки) и определяет, отвечает ли он этому селектору или нет.

Когда вы вызываете

[super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];

Вы пытаетесь отправить сообщение, используя реализацию суперкласса этого метода, которая в этом случае не существует, что приводит к сбою.

Похоже, что jjburka добрался до него первым - вам нужно позвонить

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

Кроме того, он не будет сбой, если вы используете -[super performSelector:] из нераспознанного селектора - выполнить селектор получает экземпляры реализации селектора. Он будет терпеть крах из бесконечной рекурсии.

Ответ 5

Как резюме для кого-то с одним и тем же случаем, в исходном вопросе есть две проблемы:

  • Проверка соответствия суперкласса этому селектору, который, как предлагает @jjburka, лучше всего использовать с помощью instancesRespondToSelector:.
  • Фактически вызывать метод суперкласса без компилятора, даже если он объявлен в закрытом заголовке. Это можно аккуратно реализовать, обновив его в категории (см. этот вопрос).

Объединяя это, получаем:

// … In subclass implementation file
@interface UIScrollView () <UIGestureRecognizerDelegate> @end

// … In gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
if ([UIScrollView instancesRespondToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
    return [super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
}