首先定义一个类
@interface SLAnimal : NSObject
@property(strong,nonatomic)NSString * sl_name;
- (void)saySomething;
@end
@implementation SLAnimal
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.sl_name);
}
@end
在ViewController中 通过以下两种方式调用saySomething
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//下面这两种方式调用
//方式一
Class cls = [SLAnimal class];
void *sl = &cls;
[(__bridge id)sl saySomething];
//方式二:常规调用
SLAnimal *a = [SLAnimal alloc];
[a saySomething];
}
@end
输出结果如下
2023-02-21 09:22:09.591688+0800 aaa[2244:35170] -[SLAnimal saySomething] - <ViewController: 0x7fb0daf070e0>
2023-02-21 09:22:09.591778+0800 aaa[2244:35170] -[SLAnimal saySomething] - (null)
[a saySomething]的本质是对象发送消息,a的isa指向类SLAnimal,即a的首地址指向SLAnimal的首地址,可通过LGPerson的内存平移找到cache,在cache中查找方法
[(__bridge id)sl saySomething]中的sl是来自于SLAnimal这个类,有一个指针sl,将其指向SLAnimal的首地址。
因此,a和sl都是是指向SLAnimal类的结构,都在SLAnimal中的methodList中查找方法
方式2:[a saySomething]
对于常规调用,self此时指向的是a的内存结构,获取sl_name是通过内存平移8字节获取
方式1:[(__bridge id)sl saySomething]
sl表示8字节指针,self.sl_name的获取,相当于sl首地址也需平移8字节找sl_name。sl是存在栈
中的,栈是一个先进后出
的结构,参数传入就是一个不断压栈的过程
- 隐藏参数压入栈,每个方法搜有两个隐藏参数(id self,sel _cmd)
- 隐藏参数压栈的过程,地址是递减的,栈是由高地址-->低地址分配的
- super底层调用objc_msgSendSuper,其第一个参数是一个结构体__rw_objc_super(self,class_getSuperclass),以下定义结构体,判断内部成员压栈情况。
20先加入,再加入10,所以栈中结构体内部
的成员是反向
压入栈,即低地址->高地址
,是递增的。
检验完结构体内部成员的压栈情况后,回到最初的问题,此时栈中从高到低的顺序:self --> _cmd --> (id)class_getSuperclass(objc_getClass("ViewController")) --> self - ->cls --> sl --> a
self
和_cmd
是viewDidLoad
方法的两个隐藏参数(id self,sel _cmd),是高地址->低地址正向压栈
的class_getSuperClass
和 self
为objc_msgSendSuper2
中的结构体成员,是从最后一个成员变量,即低地址->高地址反向压栈
的
可以通过代码检验
其中发现
class_getSuperclass
是ViewController,
由于objc_msgSendSuper2
返回的是当前类
,两个self
,并不是同一个self,而是栈的指针不同,但是指向同一片内存空间
[(__bridge id)sl saySomething] 调用时,此时的sl是 SLAnimal:
0x7ff7befcab18,所以saySomething
方法中传入的self
还是SLAnimal,但并不是我们通常认为的SLAnimal,使我们当前传入的消息接收者
,即SLAnimal:
0x7ff7befcab18,是SLAnimal的实例对象,此时的操作与普通的SLAnimal是一致的,即SLAnimal的地址内存平移8字节
普通a流程:a-->sl_name-内存平移8字节
sl流程:
0x7ff7befcab18 + 0x80 --> 0x7ff7befcab20, 即为self
所以sl查找sl_name时候“欺骗”了编译器, 在栈中内存平移8字节(0x7ff7befcab18 + 0x80 = 0x7ff7befcab20 = ViewController)
补充
哪些东西在栈里 哪些在堆里
alloc
的对象 都在堆
中
指针、对象
在栈
中,例如person指向的空间
在堆
中,person所在的空间在栈中
临时变量
在栈
中
属性值
在堆
,属性随对象是在栈
中注意:
堆
是从小到大,即低地址->高地址栈是从大到小,即从高地址->低地址分配
函数隐藏参数会
从前往后
一直压,即从高地址->低地址 开始入栈
,结构体内部的成员是
从低地址->高地址
一般情况下,内存地址有如下规则
0x6
开头表示在堆
中
0x7
开头的地址表示在栈
中
0x1
开头的地址表示在全局区域
中