03 - 对象,联合体和isa探索

OC对象本质探索

相信大家都听说过OC对象的本质 其实就是 结构体,但是大多数开发者不太清楚它的底层实现,接下来我们就探索一下:

Clang

  • clang是一个由Apple主导编写,基于LLVMC/C++/OC编译器
  • 主要是用于底层编译,将一些文件输出成c++文件,例如main.m输出成main.cpp,其目的是为了更好的观察底层的一些结构及实现的逻辑,方便理解底层原理

接下来我们对OC对象本质的探索就需要使用Clang编译器

  • 首先我们创建一个Mac Comman Line Tool 空工程


    Comman Line Tool
  • main.m中自定义一个类`LPerson ,并添加一个属性name
@interface LPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LPerson
@end
  • 接下来便需要使用Clangmain.m编译成main.cpp,命令如下,这里我们使用第一种:
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进行了 一些封装,要更好用一些

使用第一种这里可能会报错'UIKit/UIKit.h' file not found,这时改用第二种命令就可以了

编译报错

  • 打开编译好的main.cpp,找到LPerson的定义,发现LPerson在底层会被编译成 struct 结构体.
LPerson 结构体

由此可见LPerson对象在C++底层是结构体;

  • 由上面的LPerson结构体中我们还发现,其中还有个属性NSObject_IMPL,从代码中我们可以看出,它就是isa,是继承自NSObject,属于伪继承,伪继承的方式是直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson 拥有NSObject中的所有成员变量`。
    NSObject_IMPL
NSObject源码
NSObject源码

从上面源码也可以看出对象就是这样一个结构体,且被typedef为id 。在Foundation层id就表示一个对象 。此时便能证明,OC对象的本质 其实就是 结构体!

  • 从上面源码中我们也可以看到,LGPerson中的第一个属性 NSObject_IVARS 等效于 NSObject中的 isa,是否记得我们用lldb命令po 一个对象(LPerson)的时候的返回的是<LGPerson: 0x101404a30>,这里便是它的isa.

关于isa

通过上面的分析我们可以了解到,在对象结构体内,有一个Class类型的 isa 变量,他是一个指向该对象类型的指针 ,在面向对象编程中,对象是由类创建的,对象可以通过isa 变量找到自己所属的类。

Class探索

下面是Class的源码:

Class结构

Class源码中我们可以看到它的结构,类里面有个 super_class ,指向了类的父类; 同时类也有一个 isa指针,那类的 isa 指针指向了哪里呢?

对象是按照 所定义的各个属性和方法生产的,类 作为对象的模板,也可看成是对象。正如工厂里面的模子也是要专门制作模子的机器生产。元类 (meta class)就是设计、管理类(class)的模板。对象是 类 的实例,类是 元类 的实例。

所以类的isa指针指向了 元类

如果按照这个规则,聪明的你应该想到了问题了: 元类也是对象,元类对象中也有isa,那么元类的 isa 又指向哪里呢?总不能指向元元类吧……这样是无穷无尽的。

Objective-C语言的设计者已经考虑到了这个问题,所有元类的isa 都指向 根元类(meta Root Class)。关于实例对象、类、元类之间的关系,苹果官方给了一张图,非常清晰的表明了三者的关系。

isa
  • 实线是 super_class 指针,虚线是isa 指针。
  • Root class(class) 通常是 NSObject,NSObject 是没有超类的,所以 Root class(class)的 superclass 指向 nil
  • 每个Class都有一个 isa指针指向唯一的 Meta class
  • Root class(meta)的 superclass 指向 Root class(class),也就是 NSObject,形成一个回路。
  • 每个 Meta class 的 isa 指针都指向 Root class(meta)。

一个对象 可以通过isa找到类,根据类的isasuper_class 找到 元类父类 ,进而直到 根元类根类 ,所以 对于最开始的例子LPerson *p = [[LPerson alloc] init]; ``LPerson可以调用NSObject的方法,在这中间isa 起到至关重要的作用。

由此我们可以得出结论:

  • Object-C 是基于类的对象系统。每一个对象都是一些类的实例;这个对象的isa 指针指向它所属的类。

    • 该类描述这个 对象的数据信息 :内存分配大小(allocation size)和实例变量的类型(ivar types )与布局(layout);
    • 也描述了 对象的行为 :它能够响应的选择器(selectors)和它实现的实例方法(instance methods)。
  • 每个 Object-C 类也是一个对象,它的 isa 指针指向元类,元类是关于类对象的描述,就像类是普通实例对象的描述一样。

  • 一个元类根元类的实例根元类是它自身的实例

  • isa 指针链以一个环结束:实例指向类-指向元类-指向根元类-到自身。元类的 isa 指针并不重要,因为在现实世界中,没人会向元类对象发送消息。

isa_t

isa_t最初出现在之前的alloc探索中,通过alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone-->initInstanceIsa方法路径,查找到initIsa

initIsa

从源码中可以看出,isa_t是通过联合体(union)定义的

isa_t

isa_t类型使用联合体的原因也是基于内存优化的考虑,这里的内存优化是指在isa指针中通过char + 位域(即二进制中每一位均可表示不同的信息)的原理实现。通常来说,isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能

从isa_t的定义中可以看出:

  • 提供了两个成员,clsbits,由联合体的定义所知,这两个成员是互斥的,也就意味着,当初始化isa指针时,有两种初始化方式

    • 通过cls初始化,bits无默认值

    • 通过bits初始化,cls有默认值

  • 还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本 arm64(对应ios 移动端) 和 x86_64(对应macOS),以下是它们的一些宏定义,如下图所示

位域的宏定义
  • nonpointer有两个值,表示自定义的类等,占1位

    • 0纯isa指针
    • 1:不只是类对象地址,isa中包含了类信息、对象的引用计数
  • has_assoc 表示关联对象标志位,占1

  • 0没有关联对象

  • 1存在关联对象

  • has_cxx_dtor 表示该对象是否有C++/OC的析构器(类似于dealloc),占1位

    • 如果有析构函数,则需要做析构逻辑
    • 如果没有,则可以更快的释放对象
  • shiftclx表示存储类的指针的值(类的地址), 即类信息

    • arm64中占 33位,开启指针优化的情况下,在arm64架构中有33位用来存储类指针
    • x86_64中占 44
  • magic 用于调试器判断当前对象是真的对象 还是 没有初始化的空间,占6

  • weakly_refrenced是 指对象是否被指向 或者 曾经指向一个ARC的弱变量

    • 没有弱引用的对象可以更快释放
  • deallocating 标志对象是是否正在释放内存

  • has_sidetable_rc表示 当对象引用计数大于10时,则需要借用该变量存储进位

  • extra_rc(额外的引用计数) --- 表示该对象的引用计数值,实际上是引用计数值减1

    • 如果对象的引用计数为10,那么extra_rc为9
isa存储情况
isa指针 位域算法

这里是macOS环境,所以是x86_64

  • 通过main中的LGPerson 断点 --> initInstanceIsa --> initIsa --> 走到else中的 isa初始化,执行lldb命令:p newisa,得到newisa的详细信息

    lldb调试

  • 然后继续往下执行,走到newisa.bits = ISA_MAGIC_VALUE;下一行,表示为isabits成员赋值,重新执行lldb命令p newisa,得到的结果如下

    newisa.bits赋值之后

我们发现,newsize的信息发生了变化:


newsizes数据幻化
  • 其中magic59是由于将isa指针地址转换为二进制,从47(因为前面有4个位域,共占用47位,地址是从0开始)位开始读取6位,再转换为十进制,如下图所示
    magic是59

联合体(union)

构造数据类型的方式有以下两种:

  • 结构体struct
  • 联合体union,也称为共用体
结构体

结构体是指把不同的数据组合成一个整体,其变量是共存的,变量不管是否使用,都会分配内存

  • 缺点:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节(128位)的内存,但是在使用时,你只使用了4字节(32位),剩余的12字节(96位)就是属于内存的浪费

  • 优点:存储容量较大包容性强,且成员之间不会相互影响

联合体

联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉

  • 缺点:包容性弱

  • 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间

两者的区别

  • 内存占用情况

    • 结构体的各个成员会占用不同的内存,互相之间没有影响
    • 共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员
  • 内存分配大小

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