iOS底层原理(三) isa分析

以下源码分析基于 objc4-781

对象的isa初始化

+[NSObject alloc] 流程分析中,我们最终找到了对象创建的方法

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

在上面的方法中主要做了3件事:

  • size = cls->instanceSize(extraBytes) 计算对象占用内存的大小。
  • obj = (id)calloc(1, size) 创建对象。
  • obj->initInstanceIsa(cls, hasCxxDtor) 初始化对象的 isa

id 类型

调用 calloc 方法将结果强转成 id 类型,在源码中我们可以看到 id 类型的定义

// 定义在 Public Headers/objc.h
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

// 定义在 Project Headers/objc-private.h 中 line 82
struct objc_object {
    isa_t isa;
}

/// A pointer to an instance of a class.
typedef struct objc_object *id;

我们可以在源码中找到2个版本的 struct objc_object 定义,一个在 Public Headers 目录下,另一个在 Project Headers 前者是暴露给开发者的,后者是内部使用的,所以 id 类型本质就是一个结构体指针,struct objc_object 只有一个成员变量 isa_t isa

调用 calloc 方法,只是从系统中申请了一片内存空间,这个时候这块内存空间中没有任何内容,通过 obj->initInstanceIsa 初始化对象中的 isa

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

initInstanceIsa 方法中调用了 initIsa(cls, true, hasCxxDtor)

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);
#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
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // 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;
    }
}

intiIsa 方法中,主要就是对 isa 进行初始化。

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_t 是一个联合体,它和结构体有什么区别?

结构体

结构体是把不同的数据组合成一个整体,其变量是共存的,无论变量使用与否,都会分配内存。

结构体占用内存为其所有成员占用内存的总和。

  • 优点:存储容量较大,包容性强,成员之间不会互相影响。
  • 缺点:所有属性都会分配内存。
联合体

联合体也是由不同的数据类型组成的,但是其变量是互斥的,所有的成员占用同一段内存。而联合体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员变量赋值,就会将原来的成员赋值。

联合体占用内存为其最大成员占用的内存。

  • 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同事也节省了内存空间。
  • 缺点:包容性弱。

union isa_t 结构中,有三个成员 Class cls uintptr_t bitsstruct { ISA_BITFIELD; } 他们占用内存大小都是 8bytes,因此整个联合体占用内存 8bytes

其中 struct { ISA_BITFIELD; } 是一个位域,用来描述当联合体的值是 bits 是每一位所代表的意义

image.png

ISA_BITFIELD 是一段宏,展开后如上图。他有两个版本, __arm64__ 表示真机上位域的信息, __x86_64__ 表示模拟器上的位域信息。

nonpointer

用来标识 isa 是不是一个单纯的 Class 指针

  • 0:isa 储存一个 Class 指针地址。
  • 1: isa 不仅包含了 Class 指针地址,还储存了一下其他的信息。
has_assoc

用来标识是不是又关联对象

  • 0:没有关联对象
  • 1:存在关联对象
has_cxx_dtor

标识对象有没有析构器,如果没有析构函数,可以更快的释放

  • 0:没有析构器
  • 1:存在析构器
shiftclx

用来储存指针地址,在真机环境中最大的指针地址为 0x1000000000,转换成二进制为 1 0000 0000 0000 0000 0000 0000 0000 0000 0000, 长度为37位, 且指针地址为8字节内存对齐,由于内存对齐的原因实际上 0x1000000000 也不是有效的地址,低三位都为0,所以指针的实际有效位数为33位。同样的在模拟器中最大的指针地址为 0x7fffffe00000 对应二进制47位, 除去低三位,有效的指针位数为44位。

magic

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

weakly_refrenced

表示对象是否指向或者曾经指向一个ARC的弱引用变量。

deallocating

表示对象是否正在被释放。

has_sidetable_rc

判断该对象的引用对象是否过大,如果过大则需要其他散列表来进行存储。

extra_rc

存放该对象的引用计数减1后的结果,如果引用计数超过1,会存在这个里面,如果引用计数为10,extra_rc 的值就为9。

你可能感兴趣的:(iOS底层原理(三) isa分析)