美文网首页
OC底层原理-初探

OC底层原理-初探

作者: 卡布奇诺_95d2 | 来源:发表于2021-01-20 10:20 被阅读0次

什么是类

Class是一个指向结构体的指针。

typedef struct objc_class *Class;

此时结构体objc_class又是什么呢?
答:objc_class是继承于objc_object的结构体,或许是这个原因,类也称之为对象。
它包含父类的objc_class指针、cache、bits。由于objc_class与objc_object是继承关系,因此,在objc_class也包含了isa指针。

struct objc_class : objc_object {
    ...
    // Class ISA;
    // isa_t isa; 继承自objc_object结构体
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    ...
}
struct objc_object {
private:
    isa_t isa;
};

验证

(lldb) p/x cls
(Class) $1 = 0x0000000100008438 HQPerson
(lldb) x/8g 0x0000000100008438
0x100008438: 0x0000000100008410 0x0000000100357140
0x100008448: 0x000000010034f390 0x0000000000000000
0x100008458: 0x0000000100008080 0x00000001000ac928
0x100008468: 0x0000000000000000 0x0000000000000000
//0x0000000100008410:isa指针
//0x0000000100357140:superclass指针
//cache
//0x000000010034f390: buckets
//0x0000000000000000: cache中的union
//0x0000000100008080:bits

(lldb) po 0x0000000100008410
HQPerson    //此时是HQPerson的元类
(lldb) po 0x0000000100357140
NSObject    //此时是HQPerson的父类

isa指向

先给出结论:

  • 对像的isa指向类
  • 类的isa指向的是元类
  • 元类的isa指向根元类(NSObject)
  • 根元类的isa指向自己。

验证:

//获取对象的地址,并读取地址中的内存
(lldb) p/x person
(HQPerson *) $35 = 0x00000001006830e0
(lldb) x/4g 0x00000001006830e0
0x1006830e0: 0x011d80010000843d 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000

//解析对象中的isa
(lldb) p/x 0x011d80010000843d & 0x00007ffffffffff8
(long) $39 = 0x0000000100008438
//0x0000000100008438:对象的isa指向类

//读取类的isa
(lldb) x/4g 0x0000000100008438
0x100008438: 0x0000000100008410 0x0000000100357140
0x100008448: 0x0000000101005f10 0x0001803c00000003
//0x0000000100008410:指向元类

//读取元类的isa
(lldb) x/4g 0x0000000100008410
0x100008410: 0x00000001003570f0 0x00000001003570f0
0x100008420: 0x000000010034f390 0x0000e03500000000
//0x00000001003570f0:指向根元类
(lldb) po 0x00000001003570f0
NSObject

//读取根元类NSObject的isa
(lldb) x/4g 0x00000001003570f0
0x1003570f0: 0x00000001003570f0 0x0000000100357140
0x100357100: 0x00000001007772a0 0x0004e03100000007
//0x00000001003570f0:根元类的isa指向自己

superclass

先给出结论:

  • 类的superclass是父类;
  • 父类的superclass是 NSObject;
  • NSObject 的superclass 是nil;
  • 元类的superclass 是父元类;
  • 父元类的superclass 是根元类;
  • 根元类的superclass 是NSObject;

验证:

//读取对象的地址
(lldb) p/x person
(HQPerson *) $46 = 0x00000001006830e0

//获取对象的superclass
(lldb) p/x person.superclass
(Class) $47 = 0x0000000100357140 NSObject
//对象的superclass 是NSObject。这是因为该对象是直接继承于NSObject

//获取父类的superclass
(lldb) p/x ((NSObject*)0x0000000100357140).superclass
(Class) $48 = nil
//NSObject的superclass是nil

//根据前面知识读取元类的isa
(lldb) x/4g 0x0000000100008410
0x100008410: 0x00000001003570f0 0x00000001003570f0
0x100008420: 0x000000010034f390 0x0000e03500000000
//0x00000001003570f0:此处为元类的superclass,从地址上可以得出,0x00000001003570f0为根元类地址。

//根据前面知识读取根元类的内存时,可以根元类的superclass
(lldb) x/4g 0x00000001003570f0
0x1003570f0: 0x00000001003570f0 0x0000000100357140
0x100357100: 0x00000001007772a0 0x0004e03100000007
//0x0000000100357140:此处为根元类的superclass,从地址上可以得出,0x0000000100357140为NSObject的地址。

isa与superclass总结图

isa流程图.png

如何alloc一个对象

主要是分为三步:

  1. 根据16字节对齐原理,确定当前类的大小size。
  2. 根据size的大小开辟足够的内存空间。
  3. 将对象与类通过isa进行关联。

类的大小

类多大呢?从类的结构体中,可以很清楚的得到类的大小。
类的大小 = 16字节对齐(isa的大小 + superclass的大小 + cache的大小 + bits的大小)。

  • isa的大小:64位域,占8字节。
  • superclass的大小:指针类型,占8字节。
  • cache的大小:cache_t结构体大小 = _bucketsAndMaybeMask指针大小(8字节)+ 联合体大小(8字节) = 16字节
  • bits的大小:8字节。
  • isa的大小 + superclass的大小 + cache的大小 + bits的大小 = 8 + 8 + 16 + 8 = 40,由于需要16字节对齐,因此,类的大小为48字节。

开辟空间

已经知道类对象的大小,此时调用calloc函数,向内存中申请大小为48(instanceSize计算的大小)的内存。

obj = (id)calloc(1, size);

验证:

//调用calloc之前,通过p查看obj的内容
(lldb) p obj
nil

//调用calloc之后,再通过p查看obj的内容
(lldb) p obj
(id) $16 = 0x00000001006830e0

//calloc之后,得到一个内存地址,并且将该地址指向的内存内容清空
(lldb) x/4g 0x00000001006830e0
0x1006830e0: 0x0000000000000000 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000

将类与对象通过isa进行关联

在第二步中,开辟了一段内存空间,并将内存中的内容清空,此时需要将该内存与isa进行关联了。那如何进行关联呢?

obj->initInstanceIsa(cls, hasCxxDtor);

initInstanceIsa 函数内容

  • new一个isa_t结构体newisa
isa_t newisa(0);

(lldb) p/x newisa
(isa_t) $18 = {
  bits = 0x0000000000000000
  cls = nil
   = {
    nonpointer = 0x0000000000000000
    has_assoc = 0x0000000000000000
    has_cxx_dtor = 0x0000000000000000
    shiftcls = 0x0000000000000000
    magic = 0x0000000000000000
    weakly_referenced = 0x0000000000000000
    unused = 0x0000000000000000
    has_sidetable_rc = 0x0000000000000000
    extra_rc = 0x0000000000000000
  }
}
  • 将newisa的bits初始化为ISA_MAGIC_VALUE(0x001d800000000001ULL)
//给newisa的bits赋初值
newisa.bits = ISA_MAGIC_VALUE;

(lldb) p/x newisa
(isa_t) $19 = {
  bits = 0x001d800000000001
  cls = 0x001d800000000001
   = {
    nonpointer = 0x0000000000000001
    has_assoc = 0x0000000000000000
    has_cxx_dtor = 0x0000000000000000
    shiftcls = 0x0000000000000000
    magic = 0x000000000000003b
    weakly_referenced = 0x0000000000000000
    unused = 0x0000000000000000
    has_sidetable_rc = 0x0000000000000000
    extra_rc = 0x0000000000000000
  }
}
  • 给isa的has_cxx_dtor进行赋值
//给isa的has_cxx_dtor进行赋值
newisa.has_cxx_dtor = hasCxxDtor;

(lldb) p/x newisa
(isa_t) $22 = {
  bits = 0x001d800000000005
  cls = 0x001d800000000005
   = {
    nonpointer = 0x0000000000000001
    has_assoc = 0x0000000000000000
    has_cxx_dtor = 0x0000000000000001
    shiftcls = 0x0000000000000000
    magic = 0x000000000000003b
    weakly_referenced = 0x0000000000000000
    unused = 0x0000000000000000
    has_sidetable_rc = 0x0000000000000000
    extra_rc = 0x0000000000000000
  }
}
  • 设置isa的shiftcls。isa的shiftcls就是用来存储类指针的
//先得到类对象地址
(lldb) p/x newCls
(Class) $24 = 0x0000000100008438 HQPerson
//将类对象的地址向右移3位
(lldb) p/x 0x0000000100008438>>3
(long) $26 = 0x0000000020001087
//将得到的地址存入shiftcls
shiftcls = (uintptr_t)newCls >> 3;

(lldb) p/x newisa
(isa_t) $27 = {
  bits = 0x001d80010000843d
  cls = 0x001d80010000843d HQPerson
   = {
    nonpointer = 0x0000000000000001
    has_assoc = 0x0000000000000000
    has_cxx_dtor = 0x0000000000000001
    shiftcls = 0x0000000020001087
    magic = 0x000000000000003b
    weakly_referenced = 0x0000000000000000
    unused = 0x0000000000000000
    has_sidetable_rc = 0x0000000000000000
    extra_rc = 0x0000000000000000
  }
}
  • 设置isa的extra_rc为1,即将对象的引用计数加1
(isa_t) $28 = {
  bits = 0x011d80010000843d
  cls = 0x011d80010000843d HQPerson
   = {
    nonpointer = 0x0000000000000001
    has_assoc = 0x0000000000000000
    has_cxx_dtor = 0x0000000000000001
    shiftcls = 0x0000000020001087
    magic = 0x000000000000003b
    weakly_referenced = 0x0000000000000000
    unused = 0x0000000000000000
    has_sidetable_rc = 0x0000000000000000
    extra_rc = 0x0000000000000001
  }
}

此时可以看到,已经将obj进行了关联。

(lldb) p/x obj
(HQPerson *) $30 = 0x00000001006830e0

(lldb) x/4g 0x00000001006830e0
0x1006830e0: 0x011d80010000843d 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000

isa位域

前面一直描述,如何通过isa将类与对象进行关联,那isa是什么呢?
isa是一个64位域的联合体。

union isa_t {
    uintptr_t bits;
}

我们以x86_64为示例,来看一下64位域中到底存储了什么?

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD \
      uintptr_t nonpointer        : 1; \
      uintptr_t has_assoc         : 1; \
      uintptr_t has_cxx_dtor      : 1; \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6; \
      uintptr_t weakly_referenced : 1; \
      uintptr_t deallocating      : 1; \
      uintptr_t has_sidetable_rc  : 1; \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
  • nonpointer:表示是否对 isa 指针 开启指针优化

    • 0:纯isa指针。
    • 1:不止是类对象地址, isa 中包含了类信息、对象的引用计数等。
  • has_assoc:关联对象标志位,0没有,1存在。

  • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器.

    • 如果有析构函数,则需要做析构逻辑。
    • 如果没有,则可以更快的释放对象。
  • shiftcls : 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。x86_64 架构 有 44位。

  • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。

  • weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。

  • deallocating:标志对象是否正在释放内存。

  • has_sidetable_rc:当对象引用技术大于 10 时,则需要借用该变量存储进位。

  • extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1,例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10,则需要使用到下面的 has_sidetable_rc。

通过一个示例,直观的看一下对象的isa位域:

//HQPerson* person = [HQPerson alloc];
(lldb) p/x person
(HQPerson *) $35 = 0x00000001006830e0
//0x00000001006830e0:指向objc_object结构体的指针

(lldb) x/8g 0x00000001006830e0
0x1006830e0: 0x011d80010000843d 0x0000000000000000
0x1006830f0: 0x0000000000000000 0x0000000000000000
//0x011d80010000843d:表示isa联合体

(lldb) p/x (isa_t)0x011d80010000843d
(isa_t) $50 = {
  bits = 0x011d80010000843d
  cls = 0x011d80010000843d HQPerson
   = {
    nonpointer = 0x0000000000000001
    has_assoc = 0x0000000000000000
    has_cxx_dtor = 0x0000000000000001
    shiftcls = 0x0000000020001087
    magic = 0x000000000000003b
    weakly_referenced = 0x0000000000000000
    unused = 0x0000000000000000
    has_sidetable_rc = 0x0000000000000000
    extra_rc = 0x0000000000000001
  }
}

总结:

  • 万物皆对象。
    • 对象就是一个指向objc_object结构体的指针;
    • 类就是一个指向objc_class结构体的指针,且objc_class继承于objc_object。
  • isa与superclass的走位图。
  • 对象与类之间是通过isa进行关联。
  • 对类进行alloc操作,重要三步骤:
    • 计算类的大小size。
    • 根据size大小开辟内存空间。
    • 将对象与类通过isa进行关联。

相关文章

网友评论

      本文标题:OC底层原理-初探

      本文链接:https://www.haomeiwen.com/subject/dqnhzktx.html