iOS底层探索三-对象原理(下)

902 阅读5分钟

iOS底层探索三-对象原理(下)

前言

1、iOS底层探索一-对象原理(上)

2、iOS底层探索二-对象原理(中)

一、 对象的本质

1、对象在源文件(.cpp)的底层实现

定义一个类:Person,为了方便搜索到Person在源文件(.cpp文件)中的位置,定义在main.m文件中。

image.png

执行clang命令:clang -rewrite-objc main.m -o main.cpp,将源代码转换成源文件。(如果源代码中包含UIKit头文件,需要添加路径)

或者使用xcrun命令:xcrun -sdk iphoneos   clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp,将源代码转换成源文件。(建议使用:可以减少很多路径配置)

  • clang:是一个由Apple主导编写,基于LLVM的 C\C++\Objective-C的编译器
  • xcrun:Xcode工具、-sdk:平台,iphoneos:真机,iphonesimulator:模拟器、 -arch:基于什么架构,苹果手机是基于arm64的、-rewrite-objc:重写oc文件,-o:输出到那个文件,文件名自定义。

底层实现:

image.png

源文件中Person 对象是以结构体的形式实现,并且 struct Person_IMP 嵌套 struct NSObject_IMPLNSObject_IMPL的底层实现是一个isa指针。isa 是类的隐藏变量,是类的成员变量之一。

源文件中NSObject_IMPL的底层实现: image.png

2、对象类型的底层实现

NSObject:在源代码中,Person类是继承于NSObject,而源文件(.cpp)中 Person类是objc_object类型,因而 NSObject 的本质是objc_object。

image.png

Class:Class 类型是 objc_class 指针类型,objc_class 是一个结构体,因而Class的本质是结构体指针类型,idSEL 在底层的本质也是结构体指针,类型分别是objc_object *objec_selector *, 因此在源代码中id 类型或者是Class类型,不需要添加 * 的原因。

image.png

二、isa

1、isa_t

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

union isa_tobjc818源码中的实现。它是一个联合体类型,而联合体中的各个变量是互斥的。

2、联合体扩展

  • 结构体(struct):结构体中所有变量是“共存”的
    • 优点是“有容乃⼤”, 全⾯
    • 内存空间的分配是粗放的,不管⽤不⽤,全分配
  • 联合体(union):联合体中是各变量是“互斥”的
    • 内存使⽤更为精细灵活,也节省了内存空间
    • 缺点就是不够“包容”;

image.png 定义结构体(struct)Person1 和 联合体(union)Person2, 分别赋值输出:结构体(struct)类型person1元素可以分别赋值;联合体(union)person2元素只有最后一个元素赋值,因为联合体共用一块内存,当后一个元素赋值后,上一个元素的内存地址会被后一个元素持有。

3、ISA_BITFIELD

# if __arm64__ // 
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# 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)

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif

arm64x86_64 是两个不同的架构。arm64:iPhone,x86_64:模拟器。

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

各个成员变量在isa_t结构体中所在位域如下:

image.png

4 、位域(bit fields)扩展

位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。

image.png

定义结构体Struct1和指定位域结构体Struct2,计算两种类型结构体大小。Struct1类型4字节,Struct2类型1字节。

Struct1和Struct2位域储存:

image.png

5、shiftcls:存储类指针的值

三、对象地址如何关联到类地址

1、isa地址平移

image.png 定义类Person,移动person对象的isa地址得到只包含shiftcls的地址,对比得到和类信息地址相同

isa地址平移图解:

image.png

2、isa &= ISA_MASK

源码中isa的寻址逻辑并不是平移得到的,而是通过ISA_MASK(掩码)得到

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else
    uintptr_t clsbits = bits;
#   if __has_feature(ptrauth_calls)
#       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    if (authenticated) {
        clsbits &= ISA_MASK;
        if (clsbits == 0)
            return Nil;
        clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
    } else {
        clsbits &= objc_debug_isa_class_mask;
    }
#       else
    clsbits &= objc_debug_isa_class_mask;
#       endif
#   else
    clsbits &= ISA_MASK;
#   endif
    return (Class)clsbits;
#endif
}

按照源码方法 clsbits &= ISA_MASK得到clsbits类信息:

image.png

  • ISA_MASK:掩码,模拟器环境:低三位和高十七位抹掉;真机环境:低三位和高二十八位抹掉。

模拟器环境:对象地址64位位域[4-47]位域存储类的信息,真机环境:对象地址64位位域[4-35]位域存储类的信息

LLDB调试命令拓展

  • memory readmemory read 的简写为xmemory read xxx 读取当前对象在内存的中存储情况
  • p/x:二进制打印
  • x/4gx
    • x:读取内存的命令
    • 4:连续打印4段内存
    • g:进行格式化的输出打印 (iOS为小端,不进行格式化,输出的内容为:d0 c5 f8 0e 01 00 00 00 00 00 00 00 00 00 00 00 )
    • x:16进制显示结果