Ложноположительный 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 ответа:
Вместо
[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, что приводит к сбою!
Вот почему я предлагаю заменить [сам суперкласса] с точностью [суперкласса Б] - который решает проблему.