Objective-C对象的本质

  我们知道平时编写的OC代码,底层都是CC++代码:Objective-C -> C/C++ -> 汇编语言 -> 机器语言。所以Objective-C对象的底层实现就是CC++
  那么请思考问题:Objective-C对象,是基于C/C++什么类型的数据结构实现的?
  答案大家都知道,结构体。因为结构体可以存放不同的数据类型。想要看清OC的底层实现,我们可以使用下面①的命令行。但是编译后我们会发现,一个简单的文件就会编译出超过10万行的代码,这是因为clang在编译时候,是有不同的平台区分的,比如Windows平台,Mac平台。所以我们可以使用②的命令指定平台,这样代码就会简化很多。

1、clang -rewrite-objc main.m -o main.cpp
2、xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

  我们可以看找到一个类对象的实现,NSObject_IMPLNSObject implementation

struct NSObject_IMPL {
    Class isa;
};

  然后我们在定义一个类Student然后继续执行上面的命令,看看在底层转化是否是结构体如下所示。这里是Student_IMPL结构体的第一个成员变量就是NSObject。这样的好处就是我定义的Student类里拥有NSObject的所有属性。

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

下面有个问题,为什么NSObjectisaClass类型的,通过之前的iOS开发之alloc和init分析,我们查看方法initInstanceIsa,我们可以看到isa的类型是isa_t(如下图isa_t),在底层进行了强转,类似于swift中的as,可以查看源码(如下图强转)。

isa_t
强转

总结:

1.OC的本质是结构体
2.子类的isa继承自父类

isa_t的类型

isa_t

  在这里我们看到了一个关键词union(共用体/联合体),构造数据类型的方式,有两种:

1、结构体(struct)。
2、联合体(union,或者叫共用体)。

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

  • 缺点:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节的内存,但是在使用时,其实使用四个字节就可以表示。那么剩下的12个字节就属于浪费。
  • 优点:存储容量较大,包容性强,且成员之间不会相互影响

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

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

两者的区别
内存占用情况

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

内存分配大小

  • 结构体内存 >= 所有成员占用的内存总和(成员之间可能会有缝隙)
  • 共用体占用的内存等于最大的成员占用的内存
      我们继续回到isa_t的分析中,我们看内部实现,发现有两个变量,clsbits,上面我们讲述了共用体的特点,所以我们知道,isa的初始化方式有两种:一、通过cls初始化,那么bits没有默认值。二、通过bits初始化,cls没有默认值。而且详细的列除了isa的位域信息ISA_BITFIELD(如下图)。接下来,我们详细分析这些信息所代表的含义。
    位域

1.nonpointer

  • 0:纯isa指针
  • 1:不只是类对象地址,包含了类信息,引用计数等。

2.has_assoc表示是否有关联对象。
3.has_cxx_dtor表示该对象是否有C++/OC的析构函数。

  • 如果有析构函数,则需要做析构逻辑。
  • 如果没有,则可以更快地释放对象。

4.shiftclx表示存储类信息。arm64架构下33位,x86_64架构下占44位。
5.magic用于调试器判断当前对象是真的对象还是没有初始化的空间,占6位。
6.weakly_refrenced,是否被弱引用,没有可以更快释放。
7.deallocating表示对象是否正在释放内存。
8.has_sidetable_rc表示当对象引用计数大于10时,则需要借用该变量存储进位。
9.extra_rc(额外的引用计数) --- 导尿管表示该对象的引用计数值,实际上是引用计数值减1。如果对象的引用计数是10,那么extra_rc为9.

验证isa指针位域信息(0-64)
image.png

  上方的nonpointertrue,所以走下面的方法,进入第一个断点,我们进行lldb调试,返现newisa信息都为空,继续往下走,当走到newisa.bits = ISA_MAGIC_VALUE; define ISA_MAGIC_VALUE 0x001d800000000001ULL后,我们在进行调试(如下图)


  和上面的信息比较,我们得到了cls = 0x001d800000000001,在计算器中打开(如下图)
  我们发现从47位开始是111011,然后我们把计算器转换成10进制,输入magic = 59(如下图),同样的也是111011,在47号位置的值就是59。有没有感觉到很神奇,666!!!

  所以calloc的意义和我们之前文章里讲述的就一模一样了,是把isa和我们的类,关联起来。newisa.shiftcls = (uintptr_t)cls >> 3;,我们来进一步验证这行代码。经过赋值之后,我们看到newisashiftcls被赋值成功了,注意,我们上面是没有shiftcls的。右移三位是为了不覆盖原来的前三个位置的信息(nonpointer,has_assoc,has_cxx_dtor

你可能感兴趣的:(Objective-C对象的本质)