消息机制
OC
中的方法调用,其实就是向方法调用者(消息接受者 reciver
)发送一条消息,底层代码则是转换为 objc_msgSend
函数的调用。
比如下面示例代码:
@interface Person : NSObject
- (void)test:(int)a;
@end
@implementation Person
- (void)test:(int)a {
NSLog(@"test-%d", a);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test:1];
}
return 0;
}
转换成 C++
代码:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("test:"), 1);
}
return 0;
}
移除类型转换,简化下代码方便观察:
// 消息接受者(receiver): Person类对象。消息名称: alloc
// 消息接受者(receiver): person实例对象。消息名称: init
Person *person = objc_msgSend(objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
// 消息接受者(receiver): person实例对象。消息名称: test:
objc_msgSend(person, sel_registerName("test:"), 1);
其中 objc_getClass
用于获取类对象
,而 sel_registerName
用于获取 SEL
。
sel_registerName
和@selector
获取的地址是同一个,@selector
底层会转换成sel_registerName
。
objc_msgSend
的前两个参数固定,分别为方法调用者/对象本身/消息接受者
和要调用的方法名称 SEL
,第三个参数开始则是我们给方法增加的参数。
OC
方法调用其实就是消息机制
,给第1个参数(方法调用者/对象本身/消息接受者
)发送一条第二个参数(要调用的方法名称SEL
)的消息。
执行流程
objc_msgSend
的执行流程可以分为 3
大阶段。
消息发送
就是给方法调用者
发送一条消息,消息则是 SEL
,可以简单理解为方法名称。
在这个阶段会尝试根据方法调用者的继承体系,去对应的类对象或元类对象中查找。如果找到合适的方法则调用,如果没有找到则进入 动态方法解析
阶段。
动态方法解析
这个阶段允许开发者去 动态创建
一个新的方法,这样就可以动态调用。
如果开发者没有做任何操作去解决方法调用,则会进入最后的 消息转发
阶段。
消息转发
这个阶段有可能会转发给另外一个对象去调用,这样也可以完成方法调用。
如果这个阶段仍然没有找到合适的方法调用,就会报错 unrecognized selector sent to instance 0xxxxx
。
动态方法解析
可以实现 +resolveInstanceMethod:
或 +resolveClassMethod:
方法,动态添加对象方法或类方法实现。
动态方法解析示例:
@interface Person : NSObject
- (void)test:(int)a;
+ (void)test:(int)a;
@end
@implementation Person
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test:)) {
Method method = class_getClassMethod(self, @selector(newTest:));
class_addMethod(object_getClass(self),
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
return YES;
}
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test:)) {
// Method method = class_getInstanceMethod(self, @selector(newTest:));
// class_addMethod(self,
// sel,
// method_getImplementation(method),
// method_getTypeEncoding(method));
// 也可以用C函数方式,自己手动进行函数类型编码
class_addMethod(self, sel, (IMP)newTest, "v17@0:8i16");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void newTest(id self, SEL _cmd, int a)
{
NSLog(@"newTest-%d", a);
}
- (void)newTest:(int)a
{
NSLog(@"-newTest-%d", a);
}
+ (void)newTest:(int)a
{
NSLog(@"+newTest-%d", a);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test:1]; // newTest-1
[Person test:2]; // +newTest-2
}
return 0;
}
消息转发
重写 -forwardingTargetForSelector:
函数,在这个函数中根据 SEL
返回需要调用方法的对象:
@interface Person : NSObject
- (void)test:(int)a;
+ (void)test:(int)a;
@end
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:)) {
return [Cat class]; // 执行类方法
//return [[Cat alloc] init]; // 执行对象方法
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@implementation Cat
- (void)test:(int)a
{
NSLog(@"-[Cat test:]-%d", a);
}
+ (void)test:(int)a
{
NSLog(@"+[Cat test:]-%d", a);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test:1]; // +[Cat test:]-1
}
return 0;
}
如果没有重写 -forwardingTargetForSelector:
函数,或者重写但是返回了 nil
,则可以通过重写 -methodSignatureForSelector:
函数,返回方法签名。
然后重写 -forwardInvocation:
函数,修改方法调用者并执行:
// 方法签名: 返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:)) {
// 如果有返回则会执行forwardInvocation:,并将方法签名封装到NSInvocation中
return [NSMethodSignature signatureWithObjCTypes:"v17@0:8i16"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括: 方法调用者、方法名称、方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// int a;// 0:id 1:SEL 2:这个开始才是我们自己添加的参数
// [anInvocation getArgument:&a atIndex:2];
// anInvocation.target = [Cat new];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[Cat new]];
}
如果 -methodSignatureForSelector:
如果返回 nil
,则执行 -doesNotRecognizeSelector:
并抛出错误。
如果想转发类方法
,则下面两个函数要重写为类方法:
// 方法签名: 返回值类型、参数类型
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:)) {
// 如果有返回则会执行forwardInvocation:,并将方法签名封装到NSInvocation中
return [NSMethodSignature signatureWithObjCTypes:"v17@0:8i16"];
// 另一种创建方法签名的方式
// return [[Cat class] methodSignatureForSelector:@selector(test:)];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括: 方法调用者、方法名称、方法参数
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
// int a;// 0:id 1:SEL 2:这个开始才是我们自己添加的参数
// [anInvocation getArgument:&a atIndex:2];
// anInvocation.target = [Cat class];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[Cat class]];
}
消息转发实际应用
利用消息转发,防止指定类中任意方法找不到时报错,也就是拦截 unrecognized selector sent to instance 0xxxxx
错误:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([self respondsToSelector:aSelector]) {
return [super methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([self respondsToSelector:aSelector]) {
return [super methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
super调用
super
调用,底层会转换为 objc_msgSendSuper2
函数的调用,最少接收 2
个参数:
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver; // 消息接受者
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class; // 消息接受者的父类
#endif
/* super_class is the first class to search */
};
// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
super
调用的示例代码:
@interface Person : NSObject
- (void)test;
@end
@implementation Person
- (void)test {}
@end
@interface Student : Person
@end
@implementation Student
- (void)test
{
[super test];
}
@end
转换 C++
代码:
static void _I_Student_test(Student * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("test"));
}
拆解分析:
// 去掉类型转换代码后
objc_msgSendSuper({self, class_getSuperclass(objc_getClass("Student"))}, sel_registerName("test"));
objc_super: {
receiver: self, // Student实例对象
super_class: class_getSuperclass(objc_getClass("Student")) // Person类对象
}
SEL: sel_registerName("test") // @selector(test)
所以在 Student
中执行 [super test]
,消息的接受者还是 Student实例对象
,但是要从 Person类对象
开始搜索 test
方法实现。
class和superclass
在上面示例代码基础上增加下面代码:
@implementation Student
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"%@", [self class]);
NSLog(@"%@", [self superclass]);
NSLog(@"%@", [super class]);
NSLog(@"%@", [super superclass]);
}
return self;
}
@end
转换 C++
代码:
static instancetype _I_Student_init(Student * self, SEL _cmd) {
if (self = ((Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_Student_e3d579_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_Student_e3d579_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_Student_e3d579_mi_2, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_Student_e3d579_mi_3, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("superclass")));
}
return self;
}
拆解分析:
objc_msgSend(self, sel_registerName("class"));
objc_msgSend(self, sel_registerName("superclass"));
objc_msgSendSuper({self, class_getSuperclass(objc_getClass("Student"))},
sel_registerName("class")));
objc_msgSendSuper({self, class_getSuperclass(objc_getClass("Student"))},
sel_registerName("superclass")));
-class
和 -superclass
的实现大致是下面代码:
- (Class)class
{
return object_getClass(self);
}
- (Class)superclass
{
return class_getSuperclass(object_getClass(self));
}
所以 -class
和 -superclass
的返回值,取决于 self
,也就是消息接受者。
不管是 self
还是 super
调用,消息接受者都是 Student实例对象
,只是查找的起始类对象
不同。
最终打印结果:
Student
Person
Student
Person
利用实例对象内存结构调用方法
下面代码可以调用成功,因为实例对象地址就是 isa
的地址,而实例对象的 isa
指向类对象,下面的代码刚好利用了这一内存结构。
@interface Person : NSObject
@end
@implementation Person
- (void)test
{
NSLog(@"%@", self);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class cls = [Person class];
void *obj = &cls;
[(__bridge id)obj test];
}
return 0;
}