iOS底层探索-Alloc原理之类

404 阅读5分钟

1、对象的本质

Clang

  • Clang是一个C++编写、基于LLVM、发布于LLVM BSD协议许可证下的C/C++/Objective-C/Objective-C++轻量级编译器
  • 与GNU C语言规范几乎完全兼容,并增加额外语法特性,如C函数重载(通过__attribute__(overloadable)来修饰函数)
  • Clang将支持其普通Iambda表达式、返回类型的简化处理以及更好的处理constexpr关键字
  • Clang命令
    • clang rewrite-objc main.m(目标文件) -o main.cpp(编译文件) 把目标文件编译成C++文件
    • fatal error: 'UIKit/UIKit.h' file not found报错: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

  • 'Xcode'安装的时候也顺带安装了'xcrun'命令,'xcrun'命令在'Clang'的基础上进行了一些封装,更好用

  • xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m(目标文件) -o main-arm64.cpp(自定义编译文件名) (模拟器)

  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m(目标文件) -o main-arm64.cpp(自定义编译文件名) (手机)

对象的本质

通过上边的操作得到的编译文件可知:对象的本质就是结构体

image.png

image.png

image.png

image.png

  • 底层编译内容 image.png

  • 对比结构体查看 image.png

  • 创建对象的时候返回的地址其实就是类的第0个属性的地址,但是与结构体不同的是,的第0个属性并不是第0个成员变量,而是isa指针 image.png

  • 其实也是一个对象,即LZPerson也是一个对象

    • 类对象是系统自动帮我们创建的,里面保存了当前对象的所有方法
    • 实例对象是手动创建,其中有一个isa指针指向了创建它的类对象
    • 代码创建实例对象时,先在堆区开辟空间存放成员变量,系统自动生成类对象存放方法,实例对象isa指向类对象实现方法的调用,栈区生成局部变量地址指针person指向这块开辟的实例对象空间
    • 实例方法执行顺序:person指针->LZPerson实例对象->isa指针->LZPerson类对象->检测类对象方法->执行

image.png

  • 对象存储细节
    • 全局变量:存储在静态区,程序一启动就分配存储空间,只到程序结束才释放
    • 局部变量:存储在栈区,系统自动帮我们释放
    • 成员变量:存储在堆区(当前对象对应的堆的存储空间中),不会被自动释放,只能程序员手动释放(但是ARC会协助释放) image.png

2、结构体、联合体(共用体)、位域

结构体struct

  • 所有变量是"共存"的
    • 优点:全面,都可以赋值
    • 缺点:内存空间分配是粗放的,不管用不用全部分配 image.png

联合体union

  • 各变量是"互斥"的
    • 缺点:只有最新被赋值的一个变量正常,之前的变量内存不再使用成为脏内存
    • 优点:内存使用更精细,节省内存空间

image.png

位域:n

  • 成员后加:n(n为希望占用的位数)

image.png

3、Isa指针

  • 步骤为:计算需要的空间 --> 开辟内存 --> 创建isa指针并与空间、类关联起来 image.png

3.1、nonPointerIsa

  • isa指针开启指针优化时,就是nonPointerIsa
  • 类指针 共有8字节、64位,只存储地址过于浪费,所以苹果为了优化内存,将引用计数weak关联对象是否正在释放析构函数 等信息存到 对象 的64位isa指针里
  • nonPointerIsa 的各属性结构在 ISA_BITFIELD 中查看
ISA_BITFIELD 结构
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     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;                                       \
        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 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
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
  • 获取 isa指针中的属性内容,就需要与 ISA_MASK掩码'与'操作

3.2、创建isa指针

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    //isa_t联合体结构,用来创建isa指针
    isa_t newisa(0);           
    
    //是否对isa指针开启指针优化
    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());
        
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        /***** 引用计数 *****/
        newisa.extra_rc = 1;
    }
    
    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don't want to hurt instantiation
    isa = newisa;
}

以上作用的为创建isa指针,并判断是否开启了优化成为 nonPointIsa,如果成为了 nonPointIsa ,那么isa指针的这些属性的功能如下:

  • nonpointor:表示是否对isa指针开启指针优化
    • 0:纯isa指针
    • 1:不止是类对象地址,isa中包含了类信息、对象的引用计数等
  • 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,则需要使用has_sidetable_rc

3.2、 isa指针结构

union isa_t {           //isa_t是个联合体
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    
    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
/***** 掩码 *****/
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD// defined in isa.h 
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

Isa推导Class

  • 对象通过其isa与掩码ISA_MASK做"与"操作,可得到指针内部的class信息

image.png

image.png

  • 内存开辟后没释放则位置不变,所以要拿到类指针shiftcl可以通过对指针进行位运算,将右侧3位清空,再将左侧17位也清空再复原类指针shiftcl位置后得到:(对对象x/4gx查看内存: 0x10068ac90内存整体情况: 0x011d800100008475首地址为指针 0x0000000000000000存放的内容)

image.png

  • Isa指针64位具体分布 image.png