objc_msgSend探究

145 阅读3分钟

一、Runtime运行时理解

Objective-C Runtime Describes the macOS Objective-C runtime library support functions and data structures.
OC运行时描述macOS Objective-C运行库支持的函数和数据结构。

先看代码

#import <Foundation/Foundation.h>
@interface ApplePerson : NSObject
-(void)sayNB;
-(void)say666;
@end
@implementation ApplePerson
-(void)sayNB{
    NSLog(@"真牛逼");
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        ApplePerson *person = [[ApplePerson alloc] init];
        [person sayNB];
        [person say666];
    }
    return 0;
}

编译时候通过,但是跑的时候没有实现就直接报错,说明编译只检查语法
clang一下,看下编译后的代码

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        ApplePerson *person = ((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ApplePerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say666"));
    }
    return 0;
}

上面就是编译成C++的代码,去掉强转,可以看sayNB在转成底层代码时候变成

 objc_msgSend)(person, sel_registerName("sayNB"));

可以看出sayNB是一个方法,在底层变成一个objc_msgSend(参数1,参数2)的消息发送,可以看出参数1是消息的接收者,参数2是方法名字
验证一下,把sayNB方法名和实现改一下

-(void)sayNB:(NSString*)nbStr;

-(void)sayNB:(NSString*)nbStr{
    NSLog(@"%@",nbStr);
}

重新clang一下

ApplePerson *person = ((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ApplePerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("sayNB:"), (NSString *)&__NSConstantStringImpl__var_folders_h5_y3bndywn3fx_v059tvs_zzwh0000gn_T_main_9fefca_mi_1);
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say666"));

去掉强转,可以看到

(objc_msgSend)(person, sel_registerName("sayNB:"), (NSString *)&__NSConstantStringImpl__var_folders_h5_y3bndywn3fx_v059tvs_zzwh0000gn_T_main_9fefca_mi_1);

添加参数后,底层源码已经变了,sayNB有分号:,并且后面多了一串NSString类型的参数值


所以推出消息发送是msg_send(消息接收者,消息主体(SEL + 参数));
修改一下say666方法

-(void)say666{
    NSLog(@"666");
}

用底层代码调用

        ((void (*)(id, SEL))(void *)objc_msgSend)(person, sel_registerName("say666"));

运行打印

2021-11-19 16:51:13.911983+0800 RuntimeTest[4436:353227] 666

和用代码[person say666]完全一样,再看下msgSend方法

objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

传一个消息接收者,和方法名,改一下objc_msgSend测试

((void (*)(id, SEL))(void *)objc_msgSend)([[ApplePerson alloc] init], @selector(say666));

运行

2021-11-19 16:58:42.168515+0800 RuntimeTest[4476:358119] 666

结果一样,可知方法的本质就是消息发送

二、自定义底层调用方法


在查看C++代码中和objc_msgSend发现了objc_msgSendSuper

#define __OBJC_RW_DLLIMPORT extern
__OBJC_RW_DLLIMPORT void objc_msgSend(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper(void);

应该是对父类发消息,定义一个类AppleEmployee

#import <Foundation/Foundation.h>
#import <objc/message.h>

@interface ApplePerson : NSObject
-(void)sayNB;
-(void)say666;
@end
@implementation ApplePerson
-(void)sayNB{
    NSLog(@"%s",__func__);
}
-(void)say666{
    NSLog(@"666");
}
@end

@interface AppleEmployee : ApplePerson
-(void)sayNB;
@end
@implementation AppleEmployee
@end

AppleEmployeesayNB方法,对其父类发送消息,查看objc_msgSendSuper源码

objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

需要传入struct objc_super,查看objc_super

/// 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_super结构体,可以看出class不需要,只需要receiversuper_class,代码如下

struct objc_super apple_objc_super;
apple_objc_super.receiver = person;
apple_objc_super.super_class = [ApplePerson class];

写好方法

  ((void (*)(id, SEL))(void *)objc_msgSendSuper)((__bridge id)(&apple_objc_super), sel_registerName("sayNB"));

运行

2021-11-23 17:39:43.054548+0800 RuntimeTest[12857:1375165] -[ApplePerson sayNB]