面试题:
答案
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->中间代码->汇编、机器代码