Runtime的本质(五)

面试题:

在这里插入图片描述

答案

my name is <ViewController: 0x15f2026e0>

首先,我们看下

YZPerson *person = [[YZPerson alloc] init];
[person print];

两句代码在内存方面的存储状态:
person指针 指向 YZPerson建立的实例对象。

我们知道,[[YZPerson alloc] init]建立的实例对象,是一个结构体。结构体里面至少有一个指针,isa。还有一个成员变量_name。而且,isa是结构体的第一个成员,因此,可以说person指针 指向 YZPerson建立的实例对象的isa指针。

而,isa指向的是YZPerson类对象。

也就是,person实例对象,通过指针,找到指针指向的内容,取出前8个字节,再根据前8个字节里面的内容当做地址(isa),找到YZPerson类对象,进而在类对象里面找到print方法。

然后再看下面试题的内存存储状态:
obj->cls->YZPerson类对象
在这里插入图片描述

为什么可以打印?

person指针指向的地址cls,取出前8个字节,以前8个字节的内容为地址,找到的地方,就当做YZPerson类对象。然后在类对象里面找到print方法。因此,cls就相当于isa指针,因此,可以执行print方法。

为什么打印的是my name is <ViewController: 0x15f2026e0>

在看打印结果之前,我们先看一个其他小荔枝:

void test()
{
    long long a = 4;
    long long b = 5;
    long long c = 6;
    NSLog(@"%p, %p, %p", &a, &b, &c);
}
结果:
0x16bbd5c58, 0x16bbd5c50, 0x16bbd5c48

首先,我们知道,局部变量存储在栈上。
从结果来看,栈空间分配,内存地址是从高到低。

回到一开始的面试题,可以看出,cls的内存地址是最大的,也就是,写在最前面的,内存地址最大。

在正常情况下:
self指的是person,print方法里面的self.name,其实就是self->_name。

如何通过self找到_name?

struct YZPerson_IMPL
{
    Class isa;
    NSString *_name;
}

self(person)指向isa,然后去掉前8个字节(isa),查找后面8个字节,后8个字节的内容,就是_name的内容。
严禁来说,是isa指针地址,+8

而在本例中,self是消息接收者,而通过[(__bridge id)obj print];可以知道,消息接收者就是obj。
obj指向的cls内容地址+8,因为栈地址是从高到底,+8正好是cls前面的代码被当做_name。
obj指向的cls,cls+8
那么,如果

NSString *test = @"123";
id cls = [YZPerson class];
void *obj = &cls;
[(__bridge id)obj print];

那么打印的内容是
my name is 123
也就是,谁在cls前面,打印的就是谁的指针指向的内容。

而面试题cls前面是[super viewDidLoad];
[super viewDidLoad]
等价于:

objc_msgSendSuper({self,  [UIViewController class]}, @selector(viewDidLoad));

然后,{self, [UIViewController class]}这个是局部变量,相当于有一个隐式的局部变量:

strut abc = {self,  [UIViewController class]};
objc_msgSendSuper(abc, @selector(viewDidLoad));

这样,cls前面的第一个指针是一个结构体,第一个指针是self。因此,打印的是self指向的内容,self指向的就是viewcontroller。

在这里插入图片描述

super更深学习

我们知道,super通过指令,可以看到编译后转换为objc_msgSendSuper({self, [UIViewController class]}, @selector(viewDidLoad));
但,其实在运行的时候,不是这个类型。
在[super viewDidLoad]处打断点,然后通过Debug->Debug Workflow->Always show Disassembly可以看到汇编。
在这里插入图片描述

在这里插入图片描述

可以看到,调用的是objc_msgSendSuper2。在源码中查找这个方法,可以看到:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

虽然Class传的是当前类,但是汇编实现,是class->superclass。
也就是说,方法不一样,参数传的不一样,但最后实现的结果是一样的。

也就是,super里面的结构体第二个参数,是自己。。。不是其父类类型。虽然转换为底层代码显示是父类,但实际执行的时候,就是自己。

objective-c在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)

可以使用以下命令行指令生成中间代码
clang -emit-llvm -S main.m

Xcode软件是以LLVM为编译器
OC->中间代码->汇编、机器代码

posted @   任淏  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示