False positive respondsToSelector with UIApplicationDelegate leading to NSInvalidArgumentException

1.9k views Asked by At

In short, the following code calls an existing selector in the super class, and then gives an NSInvalidException:

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

This gives the following log exception:

  • *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[aAppDelegate applicationDidEnterBackground:]: unrecognized selector sent to instance 0x5b5d360'

To elaborate... I have a base application delegate (from our new company library) declared as:

I have a base application delegate class, BaseAppDelegate. It is declared as:

@interface CoAppDelegate : NSObject <UIApplicationDelegate> 

It implements:

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

It does not implement @selector(applicationWillResignActive:) - or at least what I mean is that I have not specifically written out code for that method. It can't be found in the .h or .m file.

My app has an app delegate that inherits from CoAppDelegate as:

@interface aAppDelegate : CoAppDelegate <UIApplicationDelegate>

I implement both of the above methods as:

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

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

When the app launches, I get the debug output "*** ACTIVE ****" - as it should.

When I send my app to the background I get that NSInvalidArgumentException stating that the responder does not exist - and it does not exist, so this is the correct exception to throw.

What I need to know is WHY does respondsToSelector give a YES when I am expecting to see a NO? What is the little subtle thing that I am missing?

3

There are 3 answers

1
Robert Wijas On BEST ANSWER

Instead of [super class] you should use [self superclass]:

[[self superclass] instancesRespondToSelector:@selector(method)]
3
Jacob Relkin On

You should use instancesRespondToSelector: for the following reason stated in the documentation:

You cannot test whether an object inherits a method from its superclass by sending respondsToSelector: to the object using the super keyword.

This method will still be testing the object as a whole, not just the superclass’s implementation. Therefore, sending respondsToSelector: to super is equivalent to sending it to self. Instead, you must invoke the NSObject class method instancesRespondToSelector: directly on the object’s superclass.

Your subclass' code should look like this:

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

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

may produces undesired results in some special cases. It is better to explicitly state class name instead of self:

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

Explanation:

Consider example:

@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

Imagine then following code:

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

This code ... crashes! Why? Let's dig through what's going on when calling bar method on C instance 'c'. [self superclass] returns... B, since self is instance of C. Of course, B instances reponds to bar, so the body of if is entered. However, [super bar] tries to call super implementation from B's perspective, so tries to call bar on A, which results in crash!

That's why I suggest to replace [self superclass] with precise [B superclass] - which solves the problem.