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指针的探索:
notion image
通过isa如何得到类地址:
notion image
现在,我们从objc的源码里,需要重新认识isa是什么了!
notion image

2.2 isa里其他字段

通过上面我们看到联合体还包含了其他有用的字段,简单罗列如下:
notion image
 
看这些字段,我们验证了如下代码,代码放在这里
其中一次验证的结果:
notion image

三、一个晦涩的实例

现在我们对isa已经了解的七七八八了,可以来看一个有趣的例子,源自sunny的一道面试题。
我作了细微改动,代码如下,
看到上面,有两个疑问?
  1. 这代码能不能跑起来?
  1. 跑起来,最终结果又是什么呢?
答案是,可以跑起来,输出如下:
my name is <ViewController: 0x7fc1c6e15df0>
那么疑问就来了,这name怎么来的。
要解释这个问题,我们还是要了解OC/C的内存布局以及函数调用栈帧:
看第一个问题:能不能跑起来?

3.1 能不能运行?

先看看平时是如何调用方法的:
再看看,这个面试题中又是如何调用的:
现在,我们将两中调用的方式作了如下的对比:
notion image
从上图可以看到:
两者在内存中的方式极其类似。我们再还原一下,一个实例对象是如何调用实例方法的过程:
  1. 根据实例对象,找到其isa指针;
  1. 根据isa找到类对象,类对象存储了实例方法,找到方法列表,找出实例方法;
  1. 调用实例方法;
不管是[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
它们内存空间分布如下:
notion image
现在我们找到了cls的第一个指针变量self,这也就是结果,在开始给出的输出:
my name is <ViewController: 0x7fc1c6e15df0>
我们还可以验证一下:
看是不是cls后的第一个变量就行:
上面的就会直接输出test的值。
另外,我们再验证下,假如不是指针变量是不是可以。
最终结果如下:
notion image

参考

链接

  1. objc4源码

示例代码

  1. 位操作
  1. isa指针
  1. isa有趣的实例