iOS 消息发送与转发
一、探索方法的本质
1.通过clang编译成cpp文件可以看到底层代码,得到方法的本质
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XXX.m
或
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk XXX.m
2.代码转换
Person *p = [Person alloc];
[p fly];
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("fly"));
((Person *(*)(id, SEL))(void *)
是类型强转
(id)objc_getClass("Person")
获取Person类对象
sel_registerName("alloc")
等同于@selector()
最后可以简写为((类型强转)objc_msgSend)(对象, 方法调用)
;
3.方法的本质
一个对象的方法像这样[obj foo]
,编译器转成消息发送objc_msgSend(obj, foo)
,所以
方法的本质是通过objc_msgSend发送消息
id objc_msgSend(id self, SEL op, ...);
id self是消息的接收者, SEL op是消息的方法名,c字符串...是参数列表
二、用msg_send
发送消息
- 先给Father和Son分别定义实例方法和类方法
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface Father: NSObject
- (void)walk;
+ (void)run;
@end
@implementation Father
- (void)walk { NSLog(@"%s",__func__); }
+ (void)run { NSLog(@"%s",__func__); }
@end
@interface Son: Father
- (void)jump;
+ (void)swim;
@end
- msg_send发送消息
1.发送实例方法(消息接收者——实例对象)
Son *s = [Son new];
objc_msgSend(s, sel_registerName("jump"));
2.发送类方法(消息接收者——类对象)
objc_msgSend(objc_getClass("Son"), sel_registerName("swim"));
3.向父类发送实例方法(receiver——实例对象;super_class——父类类对象)
struct objc_super superInstanceMethod;
superInstanceMethod.receiver = s;
superInstanceMethod.super_class = objc_getClass("Father");
objc_msgSendSuper(&superInstanceMethod, sel_registerName("walk"));
3.向父类发送类方法(receiver——类对象;super_class——父类元类对象)
struct objc_super superClassMethod;
superClassMethod.receiver = [s class];
superClassMethod.super_class = class_getSuperclass(object_getClass([s class]));
objc_msgSendSuper(&superClassMethod, sel_registerName("run"));
如果出现Too many arguments to function call, expected 0, have 2
问题,来到BuildSetting
把配置修改成如下图
三、消息发送流程
核心流程
objc_msgSend函数内部如何给对象发送消息的(消息发送流程,方法调用流程)
-
进入
objc _msgSend
函数,系统会首先判断消息接收者是不是nil
, 如果是nil
直接return
,结束该方法的调用,程序并不会崩掉。 -
如果不是
nil
, 则根据对象的isa
指针找到该对象所属的类,去这个类的方法缓存一cache
里查找方法,方法缓存是通过散列表
实现的,所以查找效率非常高,如果找到了就直接调用,如果没有找到,则会去类的方法列表methods
里查找,这里对于已排过序的方法列表采用二分查找
,对于未排过序的方法列表则采用遍历
查找,如果在类的方法列表找到了方法,则首先把该方法缓存到当前类的cache
中,然后调用该方法,如 果没有找到,则会根据当前类的superclass
指针找到它的父类,去父类里查找。 -
找到父类后,会首先去父类的方法缓存一
cache
里查找方法,如果找到了,则首先把该方法缓存到当前类的cache
中(注意不是父类的cache
),然后调用该方法,如果没有找到,则会去父类的方法列表一methods
里查找。如果在父类的方法列表找到了方法,则首先把该方法缓存到当前类的cache
中(注意不是父类的cache
),然后调用该方法,如果没有找到,则会一层一层往上,直到根类,直到nil
。 -
如果到了
nil
, 还是没有找到方法,就会触发动态方法解析。
流程图
四、消息转发流程
如果方法列表(methodLists)没找到对应的selector,系统会提供三次补救的机会。
1. 第一次
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (实例方法)
+ (BOOL)resolveClassMethod:(SEL)sel {} (类方法)
我们只需要在 resolveInstanceMethod: 方法中,利用 class_addMethod 方法,将未实现的 myTestPrint: 绑定到 myMethod 上就能完成转发,最后返回 YES。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(myTestPrint:)) {
class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
return YES;
}else {
return [super resolveInstanceMethod:sel];
}
}
void myMethod(id self, SEL _cmd,NSString *nub) {
NSLog(@"ifelseboyxx%@",nub);
}
2. 第二次
- (id)forwardingTargetForSelector:(SEL)aSelector {}
这个方法要求返回一个 id。使用场景一般是将 A 类的某个方法,转发到 B 类的实现中去。
// Son.m 中
- (id)forwardingTargetForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [Person new];
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
//Person.m中
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
3. 第三次
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}
第一个要求返回一个方法签名,第二个方法转发具体的实现。二者相互依赖,只有返回了正确的方法签名,才会执行第二个方法。 这次的转发作用和第二次的比较类似,都是将 A 类的某个方法,转发到 B 类的实现中去。不同的是,第三次的转发相对于第二次更加灵活,forwardingTargetForSelector: 只能固定的转发到一个对象;forwardInvocation: 可以让我们转发到多个对象中去。
// Son.m 中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(myTestPrint:)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
Person *person = [Person new];
Animal *animal = [Animal new];
if ([person respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:person];
}
if ([animal respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:animal];
}
}
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
@interface Animal : NSObject
@end
@implementation Animal
- (void)myTestPrint:(NSString *)str {
NSLog(@"tiger%@",str);
}
@end
⚠️ 如果到了第三次机会,还没找到对应的实现,就会 crash:
unrecognized selector sent to instance 0x7f9f817072b0
小结:流程图
五、整个消息发送与转发
转载自:https://juejin.cn/post/6980958814853939237