Ложноположительный responstoselector с UIApplicationDelegate приводит к NSInvalidArgumentException


Короче говоря, следующий код вызывает существующий селектор в суперклассе, а затем дает NSInvalidException:

- (void)applicationWillResignActive:(UIApplication *)application {
if ([super respondsToSelector:@selector(applicationWillResignActive:)])
{
    [super applicationWillResignActive:application];
}

Это дает следующее исключение журнала:

  • *** завершение работы приложения из-за неперехваченного исключения 'NSInvalidArgumentException', причина: '- [aAppDelegate applicationDidEnterBackground:]: нераспознанный селектор, отправленный в экземпляр 0x5b5d360 '

Подробнее... У меня есть базовый делегат приложения (из нашей новой библиотеки компании), объявленный как:

У меня есть базовый класс делегата приложения BaseAppDelegate. Он объявляется следующим образом:

@interface CoAppDelegate : NSObject <UIApplicationDelegate> 

Он реализует:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    DebugLog(@"*** ACTIVE ****");
}

Он не реализует @selector (applicationWillResignActive:) - или, по крайней мере, я имею в виду, что я специально не писал код для этого метода. Его нельзя найти в городе .ч или .m-файл.

У моего приложения есть делегат приложения, который наследуется от CoAppDelegate следующим образом:

@interface aAppDelegate : CoAppDelegate <UIApplicationDelegate>

Я реализую оба вышеперечисленных метода следующим образом:

- (void)applicationWillResignActive:(UIApplication *)application {
    if ([super respondsToSelector:@selector(applicationWillResignActive:)])
    {
        [super applicationWillResignActive:application];
    }
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    if ([super respondsToSelector:@selector(applicationDidBecomeActive:)])
    {   
        [super applicationDidBecomeActive:application];
    }
}

Когда приложение запускается, я получаю отладочный вывод "* * * ACTIVE * * * * " - как и должно.

Когда я отправляю свое приложение в фоновый режим, я получаю NSInvalidArgumentException, заявляющее, что ответчик не существует - и он не существует, так что это правильное исключение, чтобы бросить.

Мне нужно знать, почему responstoselector дает "да", когда я ожидаю увидеть "нет"? Что это за маленькая тонкая вещь, которую я упускаю?
3 7

3 ответа:

Вместо [super class] следует использовать [self superclass]:

[[self superclass] instancesRespondToSelector:@selector(method)]

Вы должны использовать instancesRespondToSelector: по следующей причине, указанной в документации :

Нельзя проверить, наследует ли объект метод от своего суперкласса, отправив объекту respondsToSelector: ключевое слово super.

Этот метод по-прежнему будет тестировать объект в целом, а не только реализацию суперкласса. Поэтому отправка respondsToSelector: в super эквивалентна отправке его в self. Вместо этого необходимо вызвать метод класса NSObject instancesRespondToSelector: прямо на суперкласс объекта.

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

- (void)applicationWillResignActive:(UIApplication *)application {
    if ([[self superclass] instancesRespondToSelector:_cmd])
    {
        [super applicationWillResignActive:application];
    }
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    if ([[self superclass] instancesRespondToSelector:_cmd])
    {   
        [super applicationDidBecomeActive:application];
    }
}
[[self superclass] instancesRespondToSelector:<selector>];

Может привести к нежелательным результатам в некоторых особых случаях. Лучше явно указать имя класса вместо self:

[[<ClassName> superclass] instancesRespondToSelector:<selector>];

Пояснение:

Рассмотрим пример:

@protocol MyProtocol <NSObject>
@optional
- (void)foo;
- (void)bar;
@end

@interface A : NSObject <MyProtocol>
@end

@implementation A 
- (void)foo {
     //Do sth
}
@end

@interface B : A
@end

@implementation B
- (void)bar {
    //B may not know which methods of MyProtocol A implements, so it checks
    if ([[self superclass] instancesRespondToSelector:@selector(bar)]) {
        [super bar];
    }
    //Do sth
}
@end

@interface C : B
@end

@implementation C
@end

Представьте себе следующий код:

C *c = [C new];
[c bar];

Этот код ... аварии! Почему? Давайте разберемся, что происходит при вызове метода bar на экземпляре C 'c'. [self superclass] возвращается... Б, поскольку сам является экземпляром до н. э. Конечно, в случаях reponds в бар, так что тело, если вводится. Однако [super bar] пытается вызвать super implementation с точки зрения B, поэтому пытается вызвать bar на A, что приводит к сбою!

Вот почему я предлагаю заменить [сам суперкласса] с точностью [суперкласса Б] - который решает проблему.