1、面试题
有一个Person类,有一个name属性,一个print方法,print方法打印的是name属性:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)print;
@end
- (void)print {
NSLog(@"self.name = %@", self.name);
}
请问以下代码能否运行?如果能运行,运行结果是什么?
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
}
2、能否运行
运行代码观察,可以运行,且打印结果为:
~: self.name = <ViewController: 0x7f8ab840ac70>
那么为什么能运行呢:
cls是指向Person类对象的指针,&cls是指针所在内存中的内容,可以打印一下&cls观察:
~: &cls = <Person: 0x7ffee6418268>
发现&cls在内存中是一个Person的实例对象,结合实例对象的内存结构:
实例对象底层是由isa和成员变量组成的结构体,isa是结构体的第一个元素,所以isa的地址也就是结构体的地址,所以系统会认为指向类对象的指针,在内存中就是一个有着isa的结构体,也就是一个类的实例变量,所以题中三行代码就相当于:
Person *person = [[Person alloc] init];
[person print];
cls —-> isa &cls —> [[Person alloc] init] void obj —> Person person [(__bridge id)obj print]; —> [person print];
在内存中关系如下:
由于题中三行代码和OC消息发送的内存结构是一致的,所以是可以执行的。
3、打印结果
先看一下print方法的调用,print内部打印的是name属性,也就是name成员变量的值,结合实例对象的内存结构,要找到_name,需要找打isa地址,再向下取8个字节,就是_name的值:
- (void)print {
NSLog(@"self.name = %@", self.name);
}
print方法中,self(方法调用者)其实是obj,结合上面的分析,cls被系统认成了isa,_name的值就是cls在内存中后面的8个字节的内容(比如isa地址是0x 0000,那么_name地址是0x 0008)。person打印的也就是cls后8个字节的内容。
下面再来看一下在OC方法中,局部变量的地址分配:
{
int a1 = 1;
int a2 = 1;
int a3 = 1;
NSLog(@"a1 = %p, a2 = %p, a3 = %p", &a1, &a2, &a3);
}
打印结果:
~: a1 = 0x7ffee4890258, a2 = 0x7ffee4890254, a3 = 0x7ffee4890250
由此可见,方法中局部变量地址分配是由高到低分配的,所以cls的后八个字节内容就是方法中的上一个局部变量的值。
结合之前关于super关键字的讲解,[super viewDidLoad];方法底层实现是:
struct objc_super sp = {
self, // 当前类对象
[UIViewController class], // 当前类父类的class对象
};
objc_msgSendSuper(sp, @selector(viewDidLoad));
在调用objc_msgSendSuper方法前,会先创建一个objc_super结构体,结构体内部有self和父类class对象,题中的viewDidLoad方法就相当于:
- (void)viewDidLoad {
struct objc_super sp = {
self,
[UIViewController class],
};
objc_msgSendSuper(sp, @selector(run));
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
}
方法中的局部变量内存布局和对象中成员变量内存布局如下图所示:
*图中的cls相当于isa,打印的是isa后面8个字节的数据,也就是cls后面8个字节的数据,在方法中,cls后面8个字节的数据就是super结构体中的self
所以cls后面8个字节的内容就是self(ViewController实例对象),所以打印结果是:
~: self.name = <ViewController: 0x7f8ab840ac70>