OC底层探索(四):对象的本质

210 阅读4分钟

准备

  • 想要了解本质,需要用ClangOC的类编译为底层的C/C++

  • Clang是基于LLVMC/C++/Object-C的编译器,相当于是LLVM的前端

  • 把⽬标 main.h⽂件编译成c++⽂件

clang -rewrite-objc main.m -o main.cpp  
  • UIKit报错问题: 注意SDK版本
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /
Applications/Xcode.app/Contents/Developer/Platforms/
iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m 
  • xcrun命令在clang的基础上进⾏了⼀些封装,要更好⽤⼀些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o 
main-arm64.cpp (模拟器) 
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp (⼿机) 

image.png

对象的本质及拓展

什么是对象

  • xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc WLWPerson.m -o WLWLPerson.cppWLWPerson.m 编译生成 WLWPerson.cpp

image.png

  • 打开WLWPerson.cpp, 发现代码非常的长,很难阅读。我们可以根据自己熟悉的类进行搜索定位,检索WLWPerson

1

  • 可以看出WLWPerson实际是objc_object对象的本质是objc_object结构体

image.png

  • class 类型 objc_class* Class, 是一个结构体的指针

image.png

  • id 同样也是 objc_object * 指针,所以用id的时候不需要加*

image.png

  • 生成对应的set, get 方法,我们能看到其中的隐藏参数self

image.png

  • 这是对应的get方法,我们发现取值的时候是通过self内存平移取值的,这也表明了对象内存排列,第一个是isa,然后成员变量一次排列

联合体位域

struct WLWCar1 {
    BOOL front; // 0 1
    BOOL back;
    BOOL left;
    BOOL right;
};

struct WLWCar2 {
    BOOL front: 1;
    BOOL back : 1;
    BOOL left : 1;
    BOOL right: 1;
};

// 共存
struct WLWTeacher1 {
    char        *name;
    int         age;
    double      height ;
};

// 联合体 : 成员变量互斥,共享内存
union WLWTeacher2 {
    char        *name;
    int         age;
    double      height ;
};
  • WLWCar1 内部有用4个bool, 占用4字节,也就32位,为了避免浪费,我们可以用一个字节来表示,一个字节8位,我们用每一位表示一个bool
  • WLWCar2 一个字节,二进制表示0000 1111:后边表示占用几位
  • 结构体是共存的,所有成员变量都能够同时赋值
  • 联合体则不同,成员变量之间的赋值的是互斥的,也就是只能一个有值,比如赋值了name,再赋值age,那么name就没有值了,因为他们公用一片内存空间,最后赋值的会将之前给替换段
  • 联合体可以节省内存空间

nonpointerIsa分析

image.png

  • 对象isa的初始化,可以看出isaisa_t类型

image.png

  • isa_t是一个联合体,成员变量之间是互斥的
  • defined(ISA_BITFIELD)宏,是否定义了ISA_BITFIELD, 查看发现在不同的架构定义个不相同,但大致都差不多
// __has_feature(ptrauth_calls) 只有在 A12 以上才有 arm64e
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL // 获取 cls 的掩码
#     define ISA_MAGIC_MASK  0x0000000000000001ULL // 获取 MAGIC 的掩码
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;    // 是否 nonpointer                                  \
        uintptr_t has_assoc         : 1;    // 标记优化isa指针                                   \
        uintptr_t weakly_referenced : 1;    // 是否被弱引用,没有则更快释放                                  \
        uintptr_t shiftcls_and_sig  : 52;   // 类和签名                                    \
        uintptr_t has_sidetable_rc  : 1;    // 引用计数是否存到了 sidetable 中                                    \
        uintptr_t extra_rc          : 8     // 存储该对象的引用计数值减一后的结果, 如果够存则存在这里,不够则存在 sidetable
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;   // 是否 nonpointer                                  \
        uintptr_t has_assoc         : 1;   // 标记优化isa指针                                  \
        uintptr_t has_cxx_dtor      : 1;   // 是否有C++析构函数,如果有则走析构逻辑,没有则更快释放                                   \
        uintptr_t shiftcls          : 33;  // 类 /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;   // 用于判断对象是否已经完成了初始化                                    \
        uintptr_t weakly_referenced : 1;  // 是否被弱引用,没有则更快释放                                    \
        uintptr_t unused            : 1;  // 是否正在被释放                                   \
        uintptr_t has_sidetable_rc  : 1;  // 如上                                    \
        uintptr_t extra_rc          : 19  // 如上
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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 unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
  • 为了避免浪费内存,我们可以看到isa指针如果是nonapointerIsa,64位存了很多的,不单单只是一个指针

isa推导Class

首先初始化一个对象p1然后打上断点,然后通过lldb调试

image.png

  • 我们已经知道的对象内部的排列,第一个变量就是我们的isa
  • 我们也知道isa就是 isa_t联合体,内部存了类,是否弱引用,引用计数等一些
  • 然后我们看到ISA_MASK 0x007ffffffffffff8ULL 掩码,我们通过isa与上掩码来得到我们的shiftCls也就是类
  • mask 打个比方就是一个面具,与上之后,漏出你想要的内容
  • 通过打印也可是看出对象sia中类地址,就是我们WLWPerson的类的地址
  • initIsa 就是对isa 位域的赋值
  • 如果不是nonapointerIsaisa存的就是class的地址