iOS底层原理 :isa探索分析

878 阅读4分钟

前文回顾:

iOS底层原理 :OC对象的本质

OC:结构体(struct)/位域/联合体(union)

之前已经通过clang查看过对象在C++是什么样子的了,接下来结合源码分析下对象的本质。

isa探寻

首先在源码中查看objc_class的定义,并没有发现什么有用的价值。objc_class只是包含了一个Class isa,而Class只是objc_class的一个指针别名,好一个套娃。当然,这是是OBJC里面,但是我们一般用的都是__OBJC2__。找到我们objc_class的定义,objc_class节后提继承与objc_object,因为代码太长,这里就简单显示一个截图。这个也只晓得是一个指针,指针里面放了啥还是不知道。

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

image.png 接下来只能回顾到源码对象alloc中,因为在过程中出现过initInstanceIsa,只能顺着这个查看了。顺利找到关键步骤objc_object::initIsaisa_t应该就是我们的isa了。

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    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_t定义是这样的。

union 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

然后在宏定义ISA_BITFIELD,进入宏定义看到了苹果告诉我们isa里面是怎么放的了,在ios模拟器和真机不一样,macos也不一样。

image.png

isa结构

nonpointer

nonpointer表示对指针时候开启优化 0:纯isa指针,1:不止包含对象地址,还包含类信息、对象引用计数、弱引用等。

has_assoc

has_assoc 是否有设置过关联对象,如果没有,释放时会更快

has_cxx_dtor

has_cxx_dtor 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快

shiftcls

shiftcls 存储着Class、Meta-Class对象的内存地址信息,在nonpointer=1的情况下,在arm64架构中有33位用来存储类指针 (重点)

magic

magic 用于在调试时分辨对象是否未完成初始化

weakly_referenced

weakly_referenced 是否有被弱引用指向过,如果没有,释放时会更快。弱引用表。

deallocating

deallocating 对象是否正在释放

has_sidetable_rc

has_sidetable_rc 引用计数器是否过大无法存储在isa中 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

extra_rc

extra_rc 里面存储的值是引用计数器减1,如果引用计数10这里面存的9,如果引用计数大于10则需要使用has_sidetable_rc

isa分析

  • isa_t是一个联合体,内部有成员变量bits cls,还有一个匿名结构体类型,里面包含ISA_BITFIELD这个ISA_BITFIELD使用位域完美的存放了类指针以及相关的信息,告诉我们这个isa里面存放的是什么!!
  • isa分为nonpointer类型和非nonpointer。非nonpointer类型只是一个纯指针,nonpointer还包含了类的其他信息。
  • isa是联合体+位域的方式存储信息的。采用这种方式的可以节省大量内存。万物皆对象,只要是对象就有isa指针,大量的isa就占用了很多内存,联合体公用一块内存节省了部分内存,而位域更是在节省内存的基础上存储了更多的信息,充分的将这个8字节利用的满满的,这个可以让我们很清晰的看到isa每一位存放的数据。

isa实践探究

通过initIsa来看看isa的初始过程。

1. 在objc_object::initIsa打上断点等到LQPeople来到。首先是通过isa_t的构造翻身构造了一个isa_t的联合体

image.png

2. 然后将bits赋值了ISA_MAGIC_VALUEISA_MAGIC_VALUE 为宏定义0x001f800000000001ULL ,使用二进制查看后可以看到nonpointer1magic59

image.png

3. 接下来来到了setClass,这里将cls也就是LQpeople传入了。

image.png

4. 进入到setClass,这里给shiftcls赋值了,值为cls右移3位。

image.png

5. 通过lldb查看一下newisa

image.png

6. 最后就是给extra_rc(引用计数器)赋值了。extra_rc占用8位。

image.png

7. 到这里initIsa就走完了,接下来去代码里面验证

在代码里面验证isa

这里看到p对象首地址存的就是我们刚刚的isaimage.png

isa反推class指针

1. 直接补位

找到shiftisa然后二进制查看,前面补17个零后面不3个,因为这是macos环境,所以是这样操作的。这个方法太笨了。

image.png

2.位运算

用位运算挤掉低3位以及高17位,剩下的就是我们的class指针了

image.png

3. 与上掩码

与上ISA_MASK一步到位!

image.png