我们今天主要来探索一个iOS中常见的一个面试题。
假定我们有一个类LYPerson
:
@interface LYPerson : NSObject
@property(nonatomic, copy) NSString *name;
- (void)saySomething;
@end
#import "LYPerson.h"
@implementation LYPerson
-(void)saySomething {
NSLog(@"name: %@", self.name);
}
@end
我们在ViewController
中,定义如下方法
- (void)viewDidLoad {
[super viewDidLoad];
// 代码一
Class cls = [LYPerson class];
void *p1 = &cls;
void *sp = (void *)&self;
void *end = (void *)&p1;
[(__bridge id)p1 saySomething];
}
请问:
1,saySomething
函数能成功调用么?
2,输出结果是什么?
分析
首先我们来看下如下代码的运行结果
// 代码二
LYPerson *person = [[LYPerson alloc] init];
person.name = @"LY";
[person saySomething];
对于代码二来讲:
LYPerson实例对象
调用方法时,通过isa
指针,在其类对象
的methodlist里面进行方法查找,获取name
属性值,是通过实例对象的首地址 偏移8字节
,读取name
的属性值。
了解了方法调用和属性的读取之后,我们在来分析代码一
- p1指针指向
LYPerson类对象
所以 p1能够调用 saySomething
方法。
紧接着,输出结果是什么呢?
name: <ViewController: 0x7ffa19c058f0>
为什么是 <ViewController: 0x7ffa19c058f0>
??????
首先我们来看下此时的栈空间里面的存储情况,我们陈述一些规则:
- 1,对于OC函数都有
两个隐形参数
:self
和_cmd
。 - 2,
[super viewDidLoad]
:在运行期会转化为objc_msgSendSuper({self, class_getSuperClass(objc_getClass("ViewController"))})
函数,objc_msgSendSuper
的参数为一个结构体,有两个成员变量,成员变量会从后向前依次入栈。
这样 self,_cmd,objc_msgSendSuper({self, "Viewcontroller"}) ,p1
会依次入栈。
在
saySomething
方法中
-(void)saySomething {
NSLog(@"name: %@", self.name);
}
读取name
属性值,根据LYPerson
的内存分布,需要偏移8字节,根据此时的栈内存分布,读取的值为 为 ViewController
实例。所以输出结果为 name: <ViewController: 0x7ffa19c058f0>
。
我们对代码进行稍微改动下
- (void)viewDidLoad {
[super viewDidLoad];
LYPerson *p2 = [[LYPerson alloc] init];
p2.name = @"p2 name";
Class cls = [LYPerson class];
void *p1 = &cls;
void *sp = (void *)&self;
void *end = (void *)&p1;
[(__bridge id)p1 saySomething];
}
此时的栈空间的存储情况为
所以说输出结果为name <LYPerson...>
。