开发者

False positive respondsToSelector with UIApplicationDelegate leading to NSInvalidArgumentException

开发者 https://www.devze.com 2023-02-11 10:58 出处:网络
In short, the following code calls an existing selector in the super class, and then gives an NSInvalidException:

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开发者_JS百科 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?


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

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


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


[[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.

0

精彩评论

暂无评论...
验证码 换一张
取 消