源码
从源码可知,如果动态解析条件不符合,会进入_objc_msgForward_impcache,这个方法搜不到,可猜测是汇编方法,搜 __objc_msgForward_impcache
搜到源码:
STATIC_ENTRY __objc_msgForward_impcache
MESSENGER_START
nop
MESSENGER_END_SLOW
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17
END_ENTRY __objc_msgForward
搜__objc_forward_handler,搜不到。猜测应该是C方法。搜_objc_forward_handler.搜到后无法再进行下去,因为到此后就不是开源的了。
国外小伙实现_objc_forward_handler的伪代码( __forwarding__.c ):
// 伪代码
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 僵尸对象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已经在 Runtime 注册过
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
上面代码精简成简单版后:
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}
if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
// The point of no return.
kill(getpid(), 9);
}
- 首先会调用
forwardingTargetForSelector方法,如果实现了该方法,且返回值不为空的话,继续objc_msgSend并把上面的返回值作为第一个参数传进去。 - 如果不满足上面条件,会调用methodSignatureForSelector,获取方法签名。
- 方法签名就是方法的返回值类型和参数类型。就是method_t里的types。
- 如果符合条件并实现了
forwardInvocation,先调用_invocationWithMethodSignature:frame:获取到invocation,invocation封装了方法调用,包括方法调用者,方法名,方法参数。 - 接着会调用
forwardInvocation,此时需要把传进去的invocation的target即方法调用者赋值(方法名,方法参数在方法签名时已经获取到)。 - 如果不符合条件会调用
doesNotRecognizeSelector,抛出经典错误unrecognize selector
Demo
demo1
//方法一
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[MJCat alloc] init], aSelector)
return [[MJCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 方法二
//方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0] 方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
anInvocation.target = [[MJCat alloc] init];
[anInvocation invoke];
// 或者[anInvocation invokeWithTarget:[[MJCat alloc] init]];
}
demo2
把MJClassInfo.h导入进来,不编译。main.m改成main.mm
#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
- (int)test:(int)age;
-(void)a;
-(void)b;
-(void)c;
@end
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"
@implementation MJPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:)) {
// return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
// return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 参数顺序:receiver、selector、other arguments
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// anInvocation.target == [[MJCat alloc] init]
// anInvocation.selector == test:
// anInvocation的参数:15
// [[[MJCat alloc] init] test:15]
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
-(void)a{};
-(void)b{};
-(void)c{};
@end
main.mm
#import <Foundation/Foundation.h>
#import "MJPerson.h"
#import "MJClassInfo.h"
// 消息转发:将消息转发给别人
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
mj_objc_class *personCls1 = (__bridge mj_objc_class *)[MJPerson class];
cache_t cache1 = personCls1->cache;
[person a];
[person b];
[person c];
[person test:15];
mj_objc_class *personCls = (__bridge mj_objc_class *)[MJPerson class];
cache_t cache = personCls->cache;
bucket_t *buckets = cache._buckets;
// 140734717512046 test
// 140734152267456 init
NSLog(@"init====%p",cache.imp(@selector(init)));
NSLog(@"alloc====%p",cache.imp(@selector(alloc)));
NSLog(@"test====%p",cache.imp(@selector(test:)));
NSLog(@"其他====%p",cache.imp(@selector(forwardInvocation:)));
for (int i= 0;i < sizeof(buckets);i ++) {
bucket_t buckt = buckets[i];
NSLog(@"%ld____%p",buckt._key,buckt._imp);
}
NSLog(@"哈哈哈");
}
return 0;
}
加入方法a,b,c是为了方便观察test方法是否会加入缓存。因为在[person a]打断点,会发现cache的长度是4,说明最开始缓存分配的长度是4.person初始化直接调test,后面会调其他方法,缓存长度重新分配,会被清空。就观察不到test是否会加入缓存。
在最后面打断点调试:
2020-07-24 13:11:03.271296+0800 Interview01-消息转发[59807:4433001] init====0x0
2020-07-24 13:11:03.271502+0800 Interview01-消息转发[59807:4433001] alloc====0x0
2020-07-24 13:11:03.271641+0800 Interview01-消息转发[59807:4433001] test====0x7fff6594bfdc
2020-07-24 13:11:03.271782+0800 Interview01-消息转发[59807:4433001] 其他====0x100001cb0
2020-07-24 13:11:03.272259+0800 Interview01-消息转发[59807:4433001] ==========================================
2020-07-24 13:11:03.273011+0800 Interview01-消息转发[59807:4433001] 0____0x0
2020-07-24 13:11:03.273135+0800 Interview01-消息转发[59807:4433001] 140734510489833____0x100001d90
2020-07-24 13:11:03.273265+0800 Interview01-消息转发[59807:4433001] 0____0x0
2020-07-24 13:11:03.273332+0800 Interview01-消息转发[59807:4433001] 140734152366115____0x100001cb0
2020-07-24 13:11:03.273395+0800 Interview01-消息转发[59807:4433001] 140734152365972____0x100001c10
2020-07-24 13:11:03.273458+0800 Interview01-消息转发[59807:4433001] 140734224094156____0x7fff6594bfdc
2020-07-24 13:11:03.273523+0800 Interview01-消息转发[59807:4433001] 140734717512046____0x7fff6594bfdc
2020-07-24 13:11:03.288887+0800 Interview01-消息转发[59807:4433001] 140734153143310____0x7fff659520a7
(lldb) p IMP(0x100001d90)
(IMP) $0 = 0x0000000100001d90 (Interview01-消息转发`-[MJPerson c] at MJPerson.m:53)
(lldb) p IMP(0x100001cb0)
(IMP) $1 = 0x0000000100001cb0 (Interview01-消息转发`-[MJPerson forwardInvocation:] at MJPerson.m:27)
(lldb) p IMP(0x100001c10)
(IMP) $2 = 0x0000000100001c10 (Interview01-消息转发`-[MJPerson methodSignatureForSelector:] at MJPerson.m:16)
(lldb) p IMP(0x7fff6594bfdc)
(IMP) $3 = 0x00007fff6594bfdc (libobjc.A.dylib`_objc_msgForward_impcache)
(lldb) p IMP(0x7fff659520a7)
(IMP) $4 = 0x00007fff659520a7 (libobjc.A.dylib`-[NSObject forwardingTargetForSelector:])
(lldb)
- 调用了
forwardingTargetForSelector,methodSignatureForSelector,forwardInvocation,_objc_msgForward_impcache这些方法 - 转发完成后test也会作为key(哈希值为140734717512046)存入缓存,value为
_objc_msgForward_impcache的地址值。可见源码中消息转发过程返回的IMP为_objc_msgForward_impcache的地址。 - 调用test就相当于调用
forwardInvocation. - 学习
forwardInvocation方法中NSInvocation的用法。参考:juejin.cn/post/685457…