type
status
date
slug
summary
tags
category
icon
password
一、基础
在开始探讨isa指针之前,我们要准备一些基础知识,包括位域、联合体以及内存分配的相关知识。
1.1 位域
位域表示的是,在一个结构体中,用位来存储数据。
关于位域的内存分配,有几个值得注意的点:
- 位域的最小内存不能小于位域中最大的修饰符,上面实例都是
short
,即最小不能小于short
的大小;
- 位域的最小内存不能小于所有位域字段加起来的大小,并且根据内存对齐,可能会分配多余。假设所有位域字段加起来为18位,那么就会分配32位,即4字节。
- 位域中如果有无名位域,那么无名位域会强制下一位域对齐到其下一type位域的边界。
1.2 联合体
运行结果:
关于位域和位操作,这里有一个Demo,原出于MJ。
二、isa
isa
在arm64架构前,是一个指针,指向类对象或元类对象。但是在arm64之后,是个联合体,里面包含了更丰富的实例对象的信息,存储着更多运行期使用的信息。
需要提醒的是,本系列中讨论的isa,都只针对iOS,且只针对64位。
2.1 isa
我们先回顾一下,在 Objective-C(八)对象的本质及分类(待上传),对
isa
指针的探索:通过isa如何得到类地址:
现在,我们从objc的源码里,需要重新认识
isa
是什么了!2.2 isa
里其他字段
通过上面我们看到联合体还包含了其他有用的字段,简单罗列如下:
看这些字段,我们验证了如下代码,代码放在这里:
其中一次验证的结果:
三、一个晦涩的实例
现在我们对
isa
已经了解的七七八八了,可以来看一个有趣的例子,源自sunny的一道面试题。我作了细微改动,代码如下,
看到上面,有两个疑问?
- 这代码能不能跑起来?
- 跑起来,最终结果又是什么呢?
答案是,可以跑起来,输出如下:
my name is <ViewController: 0x7fc1c6e15df0>
那么疑问就来了,这
name
怎么来的。要解释这个问题,我们还是要了解
OC/C
的内存布局以及函数调用栈帧:看第一个问题:能不能跑起来?
3.1 能不能运行?
先看看平时是如何调用方法的:
再看看,这个面试题中又是如何调用的:
现在,我们将两中调用的方式作了如下的对比:
从上图可以看到:
两者在内存中的方式极其类似。我们再还原一下,一个实例对象是如何调用实例方法的过程:
- 根据实例对象,找到其
isa
指针;
- 根据
isa
找到类对象,类对象存储了实例方法,找到方法列表,找出实例方法;
- 调用实例方法;
不管是
[obj print]
还是[person print]
,我们目的是找到类对象,此处,person
是指向实例对象的指针,当然能找到。那么!
obj
能找到吗?可以的,
obj
指向了cls
,而cls
直接指向了类对象,那么通过cls
,这个相当于isa
指针的指针,obj
是能找到类对象的。那么,既然能找到类对象,调用其实例方法也就顺理成章。
我们的目标就是:找到
cls
后第一个指针变量。3.2 运行结果的探讨
下面是我们最终的输出函数,要输出实例对象的
name
:根据上面的探讨,我们知道
cls
和实例对象person
中的isa
非常类似,在本质上,其实就是isa指针的变体。最终
person
是找到其name
属性就是直接在isa
指针后的第一个成员变量值,而且必须是指针变量,因为我们知道name
是一个指针变量。那么,我们只要找到
cls
后面第一个指针变量即可。现在,重新回到函数:
viewDidLoad
是一个函数,根据函数栈帧,局部变量分布在栈空间。现在,将以上代码转为C++代码。
焦点放在第一行,在OC中,调用
super
方法,均会将super
转换为一个结构体,这个在后面会讲到:其他几行,我们非常熟悉。
现在来看一下该函数内部的局部变量有哪些:
objc_super
临时变量
cls
obj
它们内存空间分布如下:
现在我们找到了
cls
的第一个指针变量self
,这也就是结果,在开始给出的输出:my name is <ViewController: 0x7fc1c6e15df0>
我们还可以验证一下:
看是不是
cls
后的第一个变量就行:上面的就会直接输出
test
的值。另外,我们再验证下,假如不是指针变量是不是可以。
最终结果如下: