iOS底层-对象的本质及isa原理

前言

通过分析 alloc原理内存对齐原理,只是了解了如何创建 对象alloc流程内存对齐 原则,却对 对象 的本质及 isa 知之甚少。下面具体理解一下对象的本质及isa的原理。

基本知识

位域

  • 产生:有些信息在存储时,并不需要占用一个 完整的字节,而只需占 一个几个 二进制位。例如在存放一个只有 01 两种状态时, 用 一个 二进位即可。基于这种原理,C语言提供了一种叫做 位域 的数据结构。

  • 定义:在定义结构体或联合体时,成员变量后面加 : 数字,用来限定成员变量占用的位数,这就是位域。

  • 目的:节省存储空间,处理简便。

下面通过代码理解位域的原理。

创建两个结构体,定义属性如下,一个使用位域前结构体,一个使用位域后结构体。

打印其内存结果,如下:

总结:

  • 没有位域 的结构体 s1 占用的内存空间大小为 4 字节;使用位域 的结构的大小为 1 字节。

  • $ p s3 可得 s3 的值:s3 = (a = 255, b = 255, c = NO, d = 255)

  • $ x/1bt &s3 可得内存中二进制数据为 0x00001011。低4位(从低到高)分别对应的是a、b、c、d的值;共占4个二进制位,再进行结构体的内存对齐,总大小为最大成员变量的整数倍,为 1 字节;

联合体(union)

联合体的语法和结构体非常类似,但是它们占用内存的情况却不同。

联合体(union)和结构体(struct)的差异:
  • 结构体的成员之间是 共存 的:各个成员占用不同的内存,它们互相之间没有影响。
  • 联合体的成员之间是 互斥 的:所有成员共用同一段内存,修改一个成员的值,会影响其余所有成员。
  • 结构体占用的内存:大于等于 所有成员占用内存的总和(需要内存对齐)
  • 联合体占用的内存:等于最大 的成员占用的内存,同一时刻 只能 保存一个成员的值

下面通过代码理解联合体(union)和结构体(struct)的差异。

创建一个联合体

对联合体进行赋值,并打印其内存情况:

1. 没有赋值的情况:

2. a 赋值的情况:

3. b 赋值的情况:

4. c 赋值的情况:

总结:

  • 联合体可以定义多个不同类型的成员,联合体的内存大小由其中 最大的成员的大小 决定。

  • 联合体中 修改 其中的某个变量会 覆盖 其他变量的值。

  • 联合体所有的变量 公用 一块内存,变量之间 互斥

  • 优缺点:

    • 优点:节省内存。

    • 缺点:不够包容,同一时刻 只能 保存一个成员的值。

对象的本质

如何对对象底层进行探究?首先了解一波 编译器

准备工作

  • 新建一个 Project
  • 创建如下文件,并添加属性

编译器 Clang

  • Clang 是⼀个C语⾔、C++、Objective-C语⾔的 轻量级编译器
  • Clang 将⽀持其普通 lambda 表达式、返回类型的简化处理以及更好的处理 constexpr 关键字。
  • Clang 是⼀个由 Apple 主导编写,基于 LLVM 的C/C++/Objective-C编译器。

把⽬标⽂件编译成c++⽂件

$ clang -rewrite-objc main.m -o main.cpp

如果遇到如下 UIKit 未找到的问题

执行如下指令

$ clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.4 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

  • /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 为本地 sdk 路径

  • -runtime=ios-14.4:14.4为版本号,可在下面的路径拿到。

结果如下:

编译器 xcrun

xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进⾏了⼀些封装,要更好⽤⼀些。

模拟器编译:$ xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

真机编译:$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o os-mainarm64.cpp

模拟器编译如下:

结果如下:

真机编译如下:

结果如下:

main.cpp文件分析

1. 分析对象属性

从源码分析可得:

  • NSObject 的底层实现都是 objc_object 结构体类型。

  • struct ZLObject_IMPL: 对象的底层是结构体

  • 嵌套 NSObject_IMPL 结构体。即 对象 继承于 NSObject

  • 属性以 _ 开头的,代表成员属性。

其中 NSObject_IVARS 是什么?并不知道。

2. 分析 NSObject_IMPL

NSObject_IMPL 里面只有一个成员变量是 Class isa

3. 分析 Class

main.cpp 文件中全局搜索 *Class。代码如下:

从源码分析可得:

  • Class 的底层是 objc_class 类型的结构体指针。

  • objc_object 里面也是只有一个成员变量是 Class isa

  • 泛型指针id:常用的 id是一个 objc_object 结构体指针,这就为什么平时在使用 id 修饰变量时为什么不加 *

  • SELSEL 也是结构体指针。

4. 分析 getset 方法

从源码分析可得:

  • 属性的 getset 方法中通过获取当前对象的 首地址 + 变量的偏移值 来得到当前变量,进而实现 getset 对当前变量的修改和获取。

  • 定义的属性,底层自动添加了 getset 方法。get 方法名为 _I_类名_属性名set 方法名为 _I_类名_set属性名_,例如:_I_ZLObject_name_I_ZLObject_setName_

  • 隐含参数:当前对象 self,方法 _cmd

isa的本质

在分析 alloc原理 时,跳过了对 isa 的分析,下面具体分析 isa 的原理。

回忆 alloc 流程,其流程图如下:

其中 obj->initInstanceIsa 方法如下:

最终都会执行 objc_object::initIsa 方法:

这也印证了对象的底层就是 objc_object 结构体的观点,即 对象 在调用 initIsa 方法时,也是底层 objc_object 的结构体调用 initIsa方法。

initIsa 的流程分析

1. isa_t 分析

总结:

  • isa_t 是一个联合体,联合体的成员变量存储是 互斥 的。

  • 有两个成员变量 bitscls,使用同一块内存。

2. ISA_BITFIELD 分析

其中 __has_feature(ptrauth_calls) 是什么呢?

  • __has_feature:此函数的功能是判断编译器是否支持某个功能
  • ptrauth_calls:指针身份验证,针对 arm64e 架构;使用 Apple A12 或更高版本 A 系列处理器的设备(如 iPhone XSiPhone XS MaxiPhone XR 或更新的设备)支持 arm64e 架构
  • 参考链接:developer.apple.com
  • 验证流程请参考 jr大神

针对这三种类型,其 64 位存储分布图如下:

遍历分析如下:

  • nonpointer:是否对 isa 指针开启指针优化。0 纯isa指针;1 不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等。
  • has_assoc:关联对象标志位,0 没有,1 存在
  • has_cxx_dtor:是否有 C++ 或者 Objc 的析构器,1 有析构函数,需要做析构逻辑;
    0 没有,则可以更快的释放对象。
  • shiftcls: 存储类指针的值。
  • magic:⽤于调试器判断当前对象是真的对象,还是没有初始化的空间。
  • weakly_referenced:是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。
  • deallocating:是否正在释放内存。
  • has_sidetable_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位。
  • extra_rc:对象的引⽤计数值,实际上是引⽤计数值减 1。例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤ has_sidetable_rc 存储进位。

3. 关联类的方式

方式一:位运算 (此处以 __86_64__ 为例)

类信息是存储在 isa 指针中,其中 shiftcls 是对应的对象信息。按位偏移是目的是只保留shiftcls 信息,其它位的信息 清0。最终 shiftcls 的相对位置要保持不变。如图:

方式二:与运算 isa & ISA_MASK

其中 ISA_MASK 为掩码,用于与isa指针地址与运算。值也是区分内核的:

  • __x86_64__ 内核下是 0x00007ffffffffff8

  • arm64e 内核和模拟器下是 0x007ffffffffffff8

  • arm64e 以外的其他 arm64 内核下是 0x0000000ffffffff8

方式三:原生方法 setClass

shiftcls = (uintptr_t)newCls >> 3


补充:为什么类的isa和元类的地址是一样的?

原理:isa 的结构来看,对于对象来说,没有 是否释放引用计数 等字段,存储的只有 元类 本身,所以类的 isa 和元类的地址是一样。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容