Runtime的原理探索-方法调用

/ 0

消息机制

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;
}