5

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?

Unheilig
  • 16,196
  • 193
  • 68
  • 98
Veight Zhou
  • 969
  • 1
  • 7
  • 8

1 Answers1

1

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.