OC进阶 - super本质 | 内存寻址

前期准备

1 - 本篇共用的 Person 文件

内存寻址

1 - 以下代码是否会编译成功,如果编译成功会输出什么 ?

编译可以通过:我们知道 OC 方法默认自带两个参数 self、_cmd, [obj print] 其实是给 obj 发送 print 消息;也就是说实例对象调用方法是通过 isa 找到它的类对象,然后在类对象中寻找到该方法并执行。obj 的指向是 Person 类,这一点并没有改变,所以编译通过

至于会输出什么,这里先不作答!

2 - 以下代码会输出什么

输出 my name is 123 

原因:① 局部变量存在栈区,而栈区内存分配方式是由高到低的;② 实例对象其实就是一个结构体,前 8 字节存放着 isa 指针,后面才是实例对象所拥有的成员变量,实例对象在获取成员变量的值时会跳过前 8 个字节;obj 毕竟不是实例对象,obj->name 在取 name 值时按照 OC 实例对象的取值规则就会跳过 isa 后开始取值!所以 obj 跳过 8 字节后取的值刚好是 test 变量的地址

测试一:代码会输出什么 

打印的是 objAdded 对象,输出:my name is (null)

测试二:代码会输出什么 

输出结果和 测试一 完全一样,因为 obj 指向的是 cls 地址

super 关键字

1 - 现在回过头来查看刚开始时的代码,它会输出什么 

输出:my name is <ViewController: 0x600034a9ccb0>

程序 crash

2 - 为了搞明白上面两个打印结果差异的原因,我们新建 Person 的子类 BSStudent

① 将 BSStudent.m 文件编译成 C++ 代码

注:其实 super 底层实现并不是调用 objc_msgSendSuper 函数,而是 objc_msgSendSuper2 ! C++ 代码只能作为 OC 底层实现的参考

②  如何证明 super 底层调用的是 objc_msgSendSuper2 

方式一:给一个 super 方法打上断点,查看其汇编语言即可

汇编语言(部分)

方式二:查看 Runtime 源码

注:objc_msgSendSuper2 和 objc_msgSendSuper 两者参数不同,前者时当前类,后者则是当前类的父类。虽然两者传参不同,但最终的实现效果是一样的

方式三:打印内存地址间接证明 super 底层走的是 objc_msgSendSuper2 函数。在输出语句行打上断点,并分析出局部变量的内存分布状况,会包含 super 中的参数

使用 lldb 命令

3 是 obj    的内存地址,指向 Person 类对象

5 是 super 第一个参数 self 的内存地址,是 UIViewController 的实例对象

6 是 super 第二个参数 Class 的内存地址,是 UIViewController 类对象,再次证明了 super 底层调用的是 objc_msgSendSuper2 

7 是一个未知的内存地址(可能已经出了该方法)

 

posted on 2023-03-21 00:50  低头捡石頭  阅读(44)  评论(0编辑  收藏  举报

导航