Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why resolveInstanceMethod: called twice sometimes

Recently, I'm studying the runtime in Objective-C.

I created a class named TO:

@interface TO : NSObject
@end

#import "TO.h"

@implementation TO

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%@ sel: %@", NSStringFromSelector(_cmd), NSStringFromSelector(aSelector));
    return nil;
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    NSLog(@"%@ sel: %@", NSStringFromSelector(_cmd), NSStringFromSelector(aSelector));
    return NO;
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%@ sel: %@", NSStringFromSelector(_cmd), NSStringFromSelector(sel));
    return NO;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%@ sel: %@", NSStringFromSelector(_cmd), NSStringFromSelector(sel));
    return NO;
}

+ (IMP)instanceMethodForSelector:(SEL)aSelector {
    NSLog(@"%@ sel: %@", NSStringFromSelector(_cmd), NSStringFromSelector(aSelector));
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

@end

Then, I call an unrecongnized selector somewhere:

TO *to = [TO new];
id res = [(NSString *)to uppercaseString];

Subsequently, I got the following output:

2015-12-22 22:27:04.319 OCDemo[81920:7728539] resolveInstanceMethod: sel: uppercaseString
2015-12-22 22:27:04.320 OCDemo[81920:7728539] forwardingTargetForSelector: sel: uppercaseString
2015-12-22 22:27:04.320 OCDemo[81920:7728539] resolveInstanceMethod: sel: uppercaseString
2015-12-22 22:27:04.320 OCDemo[81920:7728539] -[TO uppercaseString]: unrecognized selector sent to instance 0x7fdd3ad120a0
2015-12-22 22:27:04.322 OCDemo[81920:7728539] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TO uppercaseString]: unrecognized selector sent to instance 0x7fdd3ad120a0'

As we see, resolveInstanceMethod: was called twice.

However,if I call -[description] firstly:

TO *to = [TO new];
[to description];
id res = [(NSString *)to uppercaseString];

Then, the output would be:

2015-12-22 22:58:50.458 OCDemo[82137:7813436] resolveInstanceMethod: sel: uppercaseString
2015-12-22 22:58:50.459 OCDemo[82137:7813436] forwardingTargetForSelector: sel: uppercaseString
2015-12-22 22:58:50.459 OCDemo[82137:7813436] -[TO uppercaseString]: unrecognized selector sent to instance 0x7f9bcad59960
2015-12-22 22:58:50.461 OCDemo[82137:7813436] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TO uppercaseString]: unrecognized selector sent to instance 0x7f9bcad59960'

This time resolveInstanceMethod: was called only once.

Could someone please explain this?

like image 457
Veight Zhou Avatar asked Nov 09 '22 23:11

Veight Zhou


1 Answers

Looks like the first method invocation on given class (it will be [to description] in your example) caches the class selectors. In effect resolveInstanceMethod: is called only once for unrecognized selectors.

You can see this when you flush the method cache for given class with function

#import <objc/runtime.h>
void _objc_flush_caches(Class cls)

Modified example:

TO *to = [TO new];
[to description];
_objc_flush_caches([to class]);
id res = [(NSString *)to uppercaseString];

gives the following output:

2017-04-15 21:48:21.575527+0200 pro objective c games[29820:1116842] resolveInstanceMethod: sel: uppercaseString
2017-04-15 21:48:21.575760+0200 pro objective c games[29820:1116842] forwardingTargetForSelector: sel: uppercaseString
2017-04-15 21:48:21.575808+0200 pro objective c games[29820:1116842] resolveInstanceMethod: sel: uppercaseString
2017-04-15 21:48:21.575833+0200 pro objective c games[29820:1116842] -[TO uppercaseString]: unrecognized selector sent to instance 0x100303600
2017-04-15 21:48:21.579989+0200 pro objective c games[29820:1116842] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TO uppercaseString]: unrecognized selector sent to instance 0x100303600'

as you can see resolveInstanceMethod: is called twice, even if [to description] was called earlier. When you comment out \\_objc_flush_caches([to class]);, then resolveInstanceMethod: will be called once. Like in your second example. It suggests that the behavior you noticed is linked to method caching.

like image 82
Michał Zabielski Avatar answered Dec 13 '22 18:12

Michał Zabielski