本文主要内容

1.影响目标内存的要素
2.目标的内存散布
3.位域和联合体
4.nonPointerisa
5.怎么利用isa的位运算得到类目标
6.new办法

一、研讨影响目标内存的要素

创立工程"01_影响目标内存的要素",新建HGPerson类,给类增加一些特点和办法(有实现),如下图

iOS底层原理之对象的底层探索(下)

main.m中创立类目标,并给其特点进行赋值,经过手动核算而且打印目标需求的内存巨细(isa + 成员变量的值)和体系实践分配的内存巨细别离为40字节、48字节,发现和上一篇中定论一致:目标内部需求内存为8字节对齐,体系实践分配的内存为16字节对齐!

iOS底层原理之对象的底层探索(下)

阐明:
目标需求的内存巨细核算:isa + 成员变量的值=8+8+8+4+8+2=40(388字节对齐)
体系实践分配的内存巨细核算:16字节对齐,所认为48。

一起还得出一个定论:体系实践分配给目标的内存巨细不仅和类目标中增加的办法无关,一起也与成员变量(特点)的次序无关

所以影响目标内存的要素只要目标的成员变量的值,与其他要素无关!

弥补内容影响目标内存要素的特殊情况

如下图,自定寄父类和子类,父类中特点count在1号方位和2号方位时,体系对目标的成员变量次序不做重排!因而在1号方位时目标需求内存和体系分配的内存别离为72和80,2号方位时目标需求内存和体系分配的内存均为64

iOS底层原理之对象的底层探索(下)

iOS底层原理之对象的底层探索(下)

呈现以上问题的原因:当子类承继父类的数据结构时,父类是一块接连的内存空间,子类无法修改父类的数据结构,因而此例中”HGHuman”中count的方位不同对子类的目标的内存巨细产生了影响。
⚠️留意:在本例中替换不同的方位,内存巨细变化为16个字节(体系分配),关于内存的耗费相对很小,在开发过程中几乎能够疏忽!

二、目标的内存散布

那么体系分配给目标的内存中,数据是怎么排列的?其间的特点的内存散布会依照声明或许赋值时的次序排列吗?如下图,经过LLDB调试,x/6gx p打印目标p的内存(x/6gx详见汇编指令和LLDB常用命令汇总中的x/nuf阐明)。

iOS底层原理之对象的底层探索(下)

留意:观察内存散布,发现特点age、number所占内存共6字节,会被体系放到同一个8字节中,达到内存优化的意图!

由上图调试得出定论:目标的内存散布与特点声明或赋值次序无关,苹果体系会主动重排目标的特点的次序,达到内存优化的意图!

三、位域和联合体

1.位域
写法如下图中,”:”后边表明”位数”,1个字节8位,其规模由前面数据类型决议。假如数据类型为int,则甚至能够写成”32″。

iOS底层原理之对象的底层探索(下)

留意:

  • 后边位域的长度不能超过数据类型最大长度。如图中”a:”后边的数字不能超过7
  • 1个位域有必要存储在一个位域中,超过则存放到下一个字节。如图中“a:”后边为7,则b有必要存储到下一字节,所以图中struct3的巨细为4个字节。特别留意:”struct4″的巨细还需遵循”结构体对齐规矩”(见iOS底层原理之目标的底层探求(上))
  • 此办法用于节约内存,但因为节约内存有限,只要在体系级别才会运用,日常开发中很少运用。

2.联合体
联合体写法类似结构体,仅仅把”struct“换成”union“。经过打印结构体和联 会拓荒满足的内存给结构体,结构体会依次给对应成员变量赋值,可一起有值。而体系给联合体拓荒满足打的内存,赋值时,其间成员变量互斥,只能给某一个成员变量赋值。如下图:

iOS底层原理之对象的底层探索(下)

打印联合体内成员变量的内存发现是同一块内存

iOS底层原理之对象的底层探索(下)

联合体内存巨细核算规矩

1.有必要能够容纳最大的成员变量;
2.经过"1."核算出来的巨细有必要是其最大成员变量(根本数据类型)的整数倍。

结构体HGTeacher2内存巨细为8,联合体HGTeacher3内存巨细为8(a为7,int为4,根据规矩为8),联合体HGTeacher4内存巨细为5(根本数据类型char是1字节)。

iOS底层原理之对象的底层探索(下)

总结
结构体和联合体的区别

  • 两者内存巨细的核算规矩不同;
  • 结构体成员变量能够共存,内存拓荒粗豪;
  • 联合体成员变量互斥,能够必定的节约内存空间。

四、引出isa和nonPointerisa

回忆iOS底层原理之目标的底层探求(上),盯梢objc源码到“核算目标所需内存的巨细instanceSize函数和体系实践分配给目标的内存巨细calloc(1, size)的方位。经过剖析,由下图可知,此刻(断点1之前)分配给目标的内存并未和类绑定,而履行initInstanceIsa函数(或initIsa函数),即断点2后,经过LLDB调试发现,目标obj现已和LGPerson类绑定。因而需求研讨initInstanceIsa函数(或initIsa函数)内部即函数objc_object::initIsa的工作。

iOS底层原理之对象的底层探索(下)

进入函数objc_object::initIsa发现,创立了一个isa_t的isa,isa_t是一个联合体。
此刻引出nonPointerIsa,内存优化的手法。Isa指针是一个class类型的结构体指针,主要用来存储内存地址。Isa指针有8个字节即64位,存储类目标的内存地址无需64位空间,每个目标都存在Isa指针会导致内存的糟蹋,为了减少糟蹋,体系将与目标休戚相关的信息存在此空间(64位),如下图中的引证计数extra_rc,此刻就用到nonPointerIsa。【此处忽然引出时有疑问忽然就引出nonPointerIsa,没反应过来,其实便是剖析了Isa指针后需求进行内存优化,所以就引出!

iOS底层原理之对象的底层探索(下)

留意:
1.isTaggedPointer也是内存优化手法。
2.”newisa.extra_rc = 1;“意为:目标引证计数的值,即经过alloc创立出来的目标引证计数即为1.

前面提到isa_t是一个联合体。曾经版本均为Class类型的结构体指针,而当时遍及运用nonPointerIsa,为了兼容性所以isa_t选用联合体类型。

iOS底层原理之对象的底层探索(下)

五、怎么利用isa的位运算得到类目标

基于的当时都是nonPointerIsa,isa指针存储的内容保存到了名为ISA_BITFIELD(上图中)的结构体中。跳转到此结构体中,因为本人运用的intel64位处理器,所以运用如下图中的结构体内容:

iOS底层原理之对象的底层探索(下)

ISA_BITFIELD结构体解析

  • 低位存储nonpointerhas_assochas_cxx_dtor
  • shiftcls
  • 高位存储magicweakly_referencedunusedhas_sidetable_rcextra_rc.

每一“位”具体代表阐明如下:
nonPotinter_isa

1.nonpointer:表明是否对isa指针敞开指针优化,0为纯isa指针,1为不止是类目标地址,isa指针中包含了类信息、目标的引证计数等;
2.has_assoc:关联目标标志位,0没有,1存在;
3.has_cxx_dtor:该目标是否有C++或许Objc的析构器,假如有析构函数,则需求做析构逻辑,假如没有,则能够更快的开释目标;
4.shiftcls:存储类指针的值,敞开指针优化的情况下,在intel 64架构中由44位用来存储类指针;
5.magic:用于调试器判断当时目标是真的目标还是没有初始化的空间;
6.weakly_referenced:标志目标是否被指向或许曾经指向一个ARC的弱变量,没有弱引证的目标能够更快开释;
7.unused:标志未被运用的空间【自己理解】
8.has_sidetable_rc:当目标引证计数大于extra_rc所能存储的最大规模时,则需求借用该变量存储进位;
9.extra_rc:表明该目标的引证计数值,当目标的引证计数超出最大的存储规模时,则需求运用到上面的has_sidetable_rc。

剖析HGPerson的实例目标p,运用如下办法,获取并验证类目标的内存地址:
在intel 64架构(x86_64)中,将isa指针内存地址右移3位(清除低3位),左移20位(清除高17位),再右移17位,得到的内存地址即为类目标的内存地址。实操如下图:
模拟器调试:

iOS底层原理之对象的底层探索(下)
真机调试:

iOS底层原理之对象的底层探索(下)

弥补内容:

1.验证引证计数的值
经过内存地址的改换,也可获取到目标引证计数的值,即最高1位的值(真机):将isa指针地址右移45位(1+1+1+33+6+1+1+1),得到引证计数为2;

iOS底层原理之对象的底层探索(下)

留意⚠️:模拟器验证有问题,待解决!!!

经过探求发现运用isa & ISA_MASK(对应架构)即可得到类目标的内存地址!

模拟器:

iOS底层原理之对象的底层探索(下)

真机:

iOS底层原理之对象的底层探索(下)

2.无法打断点的问题解决 在调试过程中发现,在某处打了断点后,运行工程,在打了断点的方位不会中止,而且断点方位显现为虚线的。呈现这个问题或许是因为体系优化导致的,解决办法:找到"Build Setting" 搜索lto,设置“Link-Time Optimization”为“No”一般都可解决。假如不能解决,或许是其他原因导致的,因情况而定。

iOS底层原理之对象的底层探索(下)

六、new办法

探求new办法的实质,首要创立工程”06_new办法”,在类HGPerson,重写init办法,如下图。运用alloc-init和new别离创立目标p、p1,打印两者特点发现均已被赋值。

iOS底层原理之对象的底层探索(下)

iOS底层原理之对象的底层探索(下)

常识扩展:工厂设计形式
运用对工厂的承继然后创造出其他的子工厂,在其子工厂进行创立类的目标,后续直接调用该工厂的那个重写办法即可。如是说,NSObject中有init办法,即为工厂形式,其子类在创立时,即可用此办法进行特点等的初始化,得出不同的目标,但父类NSObject自身init没有做事情。

盯梢汇编及源码
new的办法源码如下:

iOS底层原理之对象的底层探索(下)

new办法实践调用了callAllocinit办法,callAlloc实践又调用了alloc,所以可看做new[[alloc] init]意义相同。盯梢汇编发现,先调用了objc_opt_new函数,找到对应源码同new办法同理:

iOS底层原理之对象的底层探索(下)

iOS底层原理之对象的底层探索(下)

本文总结

  1. 探求目标内存散布
  2. 探求isa指针本质
  3. 了解new办法

有任何问题,欢迎各位谈论指出!觉得博主写的还不错的麻烦点个赞喽