iOS runtime消息转发之objc_msgSend探究

引言

消息转发的本质:向对象发送消息,是一个查找方法的过程。在前面我们研究过,编译成c++本质是一个叫objc_classstruct指针

15033832-8973df891eda5a82.png

objc_class
里面有一个重要的成员cache_t,用于方法的缓存。

查找方法的过程在底层即是一个根据sel找到imp的流程。

这里在探究消息转发概念之前,不得不提到一个在Objective-C里面一个很重要的概念:runtime

  • 运行时是相对于编译时来说的
  • 编译时,主要是做一些词法语法分析,也就是编译时类型检查,编译时刻代码是不会被加载到内存里
  • 运行时:可执行文件被装载到内存中,代码跑起来了,会做运行时检查,但是和编译时的检查又不一样,不是简单的扫描代码,而是在 内存中做些操作,做些判断。

举例如下:
创建两个类WJPersonWJTeacher,并且WJTeacher 继承于WJPerson

WJTeacher:

    #import <Foundation/Foundation.h>
    #import "WJPerson.h"
    NS_ASSUME_NONNULL_BEGIN

    @interface WJTeacher : WJPerson
    //声明并实现
    - (void)playFootball;
    //只声明不实现,在父类实现
    - (void)playBasketball;

    @end

    NS_ASSUME_NONNULL_END

     #import "WJTeacher.h"

    @implementation WJTeacher


    - (void)playFootball{
        NSLog(@"%s",__func__);
    }

    @end

WJPerson:

    #import <Foundation/Foundation.h>

    NS_ASSUME_NONNULL_BEGIN

    @interface WJPerson : NSObject
    //这里方法没带参数,有兴趣的可以把带参数的试试
    - (void)playBasketball;

    @end

    NS_ASSUME_NONNULL_END

   #import "WJPerson.h"

    @implementation WJPerson
    - (void)playBasketball{
        NSLog(@"%s",__func__);
    }


    @end

在main.m

   int main(int argc, const char * argv[]) {
       @autoreleasepool {
    
    WJTeacher *p  = [WJTeacher alloc];
    [p playFootball];
    [p playBasketball];


    
    }
   return 0;
}
   2021-07-01 16:43:14.786334+0800 KCObjcBuild[1925:49713] -[WJTeacher playFootball]
   2021-07-01 16:43:14.786870+0800 KCObjcBuild[1925:49713] -[WJPerson playBasketball]

分析如上代码:在WJTeacher 我们只是声明playBasketball方法,但是控制台仍然显示该方法调用成功。为什么会这样?

首先:我们还是clang下这段代码,看看编译后到底是什么样子。

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

            WJTeacher *p = ((WJTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("WJTeacher"), sel_registerName("alloc"));
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("playFootball"));
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("playBasketball"));

 //带参数的-(void)playWithWhom:(NSString*)name;
    Dog*dog = ((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Dog"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)dog, sel_registerName("playWithWhom:"), (NSString *)&__NSConstantStringImpl__var_folders_4c_6y4z_qs972qgqg7g06frb7540000gn_T_main_48f0a4_mi_0);


        }
        return 0;
    }

从而引出了我们所要研究的主题:objc_msgSend

objc_msgSend

image.png

这里发现,objc_msgSend(void /* id self, SEL op, ... */ )方法虽然出来了,但是参数为空,需要进行一步设置

image.png
image.png

结论:[p playFootball] 等价objc_msgSend(p,sel_registerName("playFootball"))

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容