1、面试题

有一个Person类,有一个name属性,一个print方法,print方法打印的是name属性:

  1. @interface Person : NSObject
  2. @property (nonatomic, copy) NSString *name;
  3. - (void)print;
  4. @end
  5. - (void)print {
  6. NSLog(@"self.name = %@", self.name);
  7. }

请问以下代码能否运行?如果能运行,运行结果是什么?

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. id cls = [Person class];
  4. void *obj = &cls;
  5. [(__bridge id)obj print];
  6. }

2、能否运行

运行代码观察,可以运行,且打印结果为:

  1. ~: self.name = <ViewController: 0x7f8ab840ac70>

那么为什么能运行呢:
cls是指向Person类对象的指针,&cls是指针所在内存中的内容,可以打印一下&cls观察:

  1. ~: &cls = <Person: 0x7ffee6418268>

发现&cls在内存中是一个Person的实例对象,结合实例对象的内存结构:
image.png
实例对象底层是由isa和成员变量组成的结构体,isa是结构体的第一个元素,所以isa的地址也就是结构体的地址,所以系统会认为指向类对象的指针,在内存中就是一个有着isa的结构体,也就是一个类的实例变量,所以题中三行代码就相当于:

  1. Person *person = [[Person alloc] init];
  2. [person print];

cls —-> isa &cls —> [[Person alloc] init] void obj —> Person person [(__bridge id)obj print]; —> [person print];

在内存中关系如下:
image.png
由于题中三行代码和OC消息发送的内存结构是一致的,所以是可以执行的。

3、打印结果

先看一下print方法的调用,print内部打印的是name属性,也就是name成员变量的值,结合实例对象的内存结构,要找到_name,需要找打isa地址,再向下取8个字节,就是_name的值:
image.png

  1. - (void)print {
  2. NSLog(@"self.name = %@", self.name);
  3. }

print方法中,self(方法调用者)其实是obj,结合上面的分析,cls被系统认成了isa,_name的值就是cls在内存中后面的8个字节的内容(比如isa地址是0x 0000,那么_name地址是0x 0008)。person打印的也就是cls后8个字节的内容。
下面再来看一下在OC方法中,局部变量的地址分配:

  1. {
  2. int a1 = 1;
  3. int a2 = 1;
  4. int a3 = 1;
  5. NSLog(@"a1 = %p, a2 = %p, a3 = %p", &a1, &a2, &a3);
  6. }

打印结果:

  1. ~: a1 = 0x7ffee4890258, a2 = 0x7ffee4890254, a3 = 0x7ffee4890250

由此可见,方法中局部变量地址分配是由高到低分配的,所以cls的后八个字节内容就是方法中的上一个局部变量的值。
结合之前关于super关键字的讲解,[super viewDidLoad];方法底层实现是:

  1. struct objc_super sp = {
  2. self, // 当前类对象
  3. [UIViewController class], // 当前类父类的class对象
  4. };
  5. objc_msgSendSuper(sp, @selector(viewDidLoad));

在调用objc_msgSendSuper方法前,会先创建一个objc_super结构体,结构体内部有self和父类class对象,题中的viewDidLoad方法就相当于:

  1. - (void)viewDidLoad {
  2. struct objc_super sp = {
  3. self,
  4. [UIViewController class],
  5. };
  6. objc_msgSendSuper(sp, @selector(run));
  7. id cls = [Person class];
  8. void *obj = &cls;
  9. [(__bridge id)obj print];
  10. }

方法中的局部变量内存布局和对象中成员变量内存布局如下图所示:
image.png
image.png

*图中的cls相当于isa,打印的是isa后面8个字节的数据,也就是cls后面8个字节的数据,在方法中,cls后面8个字节的数据就是super结构体中的self

所以cls后面8个字节的内容就是self(ViewController实例对象),所以打印结果是:

  1. ~: self.name = <ViewController: 0x7f8ab840ac70>