OC类方法的调用 编译成c++代码后的结果分析

2021-02-08 09:17

阅读:683

假设MyObject类中有两个方法,一个类方法sayHello,一个实例方法walk,分别为

+ (void)sayHello;

-(void)walk;

 

调用方式如下:

-(void)doSomething {

   [MyObject sayHello];

   [self walk];

}

 

编成c++代码后如下:

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("sayHello"));

 

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("walk"));

 

可以看出调用方式都是通过objc_msgSend这个方法,给对象发送消息,不同的是类方法是给类对象发送消息,而实例方法是给实例发送消息。注意给类对象发送消息时类对象的获取方式,是通过objc_getClass("MyObject")这个函数拿类对象的,也就是说程序内部其实维护了一张 “字符串->类对象” 的映射表,每次通过类名调用类方法的时候都要先通过字符串找到类对象,然后再给类对象发消息,当然在类函数内通过self调用类方法时就不用通过字符串找类对象了,因为类方法中的self就是类对象。

 

小总结:通过类名调用类方法的时候都要先通过类名的字符串找到类对象,然后再给类对象发消息。

 

OC创建对象的方式:

所以可以推测OC创建对象的方式,runtime先找到类对象,然后根据类对象中的实例变量列表分配内存,当然头部有一个isa指针的空间,这样的话就成功分配内存并创建了一个对象。

 

InjectionIII的实现原理:

因此Xcode上的神器InjectionIII的实现原理就清晰了,InjectionIII新创建了一个动态链接库,把改动的类打包进去,加载到内存中之后内存中其实同时存在了两个同名的类对象,如程序中已经有一个ViewController的类对象,动态链接库载入后又会有一个ViewController的类对象,这时候全局的 “字符串->类对象”的映射表中“ViewController”这个字符串对应的类对象会被更新成新的类对象,所以新创建的ViewController实例就会是用新类对象生成的,进而会去调用新的方法实现。

 

所以可以推测,如果已经有一个老的ViewController实例,当ViewController.m文件修改后生成了新的ViewController类对象,载入内存中之后再次创建的ViewController实例就会是新的,老对象调老方法,新对象调新方法。但是通过isa指针拿两个实例的类对象的时候会发现这两个类对象虽然同名,但是却不相等,所以这可能也是以后debug的时候需要注意的一个坑。

 

所以InjectionIII的原理简单讲就是会更新全局的“字符串->类对象”映射表,用新的类对象去生成对应的实例,因此调用方法时会去调用新的类对象的方法列表中去找对应的方法。

 

实例方法与类方法编译成c++代码后的结果:

实例方法-(void)walk跟类方法+(void)sayHello编译成c++代码后示例如下:

static void _I_MyObject_walk(MyObject * self, SEL _cmd) 

static void _C_MyObject_sayHello(Class self, SEL _cmd)

可以看出无论是实例方法还是类方法,编译完成后的函数的前两个参数都是 对象 和 cmd,区别是实例方法调用时第一个参数传进来的是实例,类方法调用时第一个参数传进来的是类对象。

 


评论


亲,登录后才可以留言!