添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

那么一个Objective-C类、对象是如何通过C/C++实现、储存的呢?

源码探究

ISA

我们通过以下命令,将.m文件转化为C/C++代码。

1
clang -rewrite-objc **.m

可以获取 **.cpp文件。

在7700多行,可以获取如下结构体

1
2
3
struct NSObject_IMPL {
Class isa;
};

在objc.h头文件中,我们可以发现如下代码

1
2
3
4
5
6
7
8
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

通过注释我们得知:

  • 结构体objc_class表示一个Objective-C的类。
  • 结构体objc_object表示一个Objective-C Class的实例。
  • 那么objc_class又是什么呢?

    在runtime.h文件中,我们可以发现如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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;
    /* Use `Class` instead of `struct objc_class *` */

    我们可以得知:在Objective-C中,每个对象都是一个结构体,都有一个isa指针,类对象Class也是一个对象。在运行时,可以通过isa指针查找该对象是什么类。

    那么isa到底是什么?为什么需要isa呢?

    我们可以通过 objc4 源码获取一些信息

    objc4 的源码不能直接编译,需要配置相关环境才能运行。可以在这里下载可调式的源码。
    objc 运行时源码的入口在 void _objc_init(void) 函数。

    objc-private.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct objc_object {
    private:
    //isa是一个union联合体,包含这个对象所属类的信息
    isa_t isa;

    public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    ...
    }

    objc-runtime-new.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct objc_class : objc_object {
    // Class ISA; (ISA继承于objc_object)
    Class superclass; //当前类父类
    cache_t cache; // formerly cache pointer and vtable 缓存指针和vtable,提高方法调用效率
    class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 存储类的方法、属性、协议等信息

    // 针对 class_data_bits_t 的 data() 函数的封装,最终返回一个 class_rw_t 类型的结构体变量
    // Objective-C 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
    class_rw_t *data() {
    return bits.data();
    }
    ...
    }

    通过源码可以得知,objc_class继承于objc_object,所以二者都存在isa成员变量,类型为:isa_t。isa_t是一个union。

    我们回到objc-private.h中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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
    };

    isa_t包含一个成员变量 cls。

    每个objc_object通过自己持有的isa查找到自己所属的类。而对于objc_class来说,可以通过isa找到自己的mate class,即元类。

    对于IAS_BITFIELD,定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //__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
  • nonpointer
  • 表示 isa_t 的类型,0 表示 raw isa,也就是没有结构体的部分,访问对象的 isa 会直接返回一个指向 cls 的指针,也就是在 iPhone 迁移到 64 位系统之前时 isa 的类型。1 表示当前 isa 不是指针,但是其中也有 cls 的信息,只是其中关于类的指针都是保存在 shiftcls 中。

  • has_assoc
  • 对象含有或者曾经含有关联引用(category相关),没有关联引用的可以更快地释放内存(object dealloc相关,其他文章会说到)。

  • has_cxx_dtor
  • 表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存(object dealloc相关)。

  • shiftcls
  • 见nonpointer

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

  • weakly_referenced
  • 对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放

  • deallocating
  • 对象正在释放内存

  • has_sidetable_rc
  • 对象的引用计数太大了,存不下

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

    isa初始化

    我们可以通过isa初始化方法,来了解这64位bits作用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    inline void 
    objc_object::initIsa(Class cls)
    {
    initIsa(cls, false, false);
    }

    inline void
    objc_object::initClassIsa(Class cls)
    {
    if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) {
    initIsa(cls, false/*not nonpointer*/, false);
    } else {
    initIsa(cls, true/*nonpointer*/, false);
    }
    }

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

    initIsa(cls, true, hasCxxDtor);
    }

    inline void
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
    {
    assert(!isTaggedPointer());

    if (!nonpointer) {
    isa.cls = 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;
    }
    }

    当我们初始化一个Objective-C对象,为其分配内存时,调用栈中包含了 initInstanceIsa与initIsa这2个方法。

    initInstanceIsa方法,传入的nonpointer为true。所以initIsa方法可以简化为( ARM_ARCH_7K 应该是watch指令集,所以我们只看else部分):

    1
    2
    3
    4
    5
    6
    7
    8
    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
    isa_t newisa(0);
    newisa.bits = ISA_MAGIC_VALUE;
    newisa.has_cxx_dtor = hasCxxDtor;
    newisa.shiftcls = (uintptr_t)cls >> 3;
    isa = newisa;
    }

    先将 newisa 的 bits 赋值为常量 ISA_MAGIC_VALUE

    1
    #define ISA_MAGIC_VALUE 0x000001a000000001ULL

    里面包括了 magic 和 nonpointer 的值。然后将是否有 C++ 析构函数标示上,最后将位移(shift)后的 cls 存入 shiftcls。

    1
    newisa.shiftcls = (uintptr_t)cls >> 3;

    将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。

    绝大多数机器的架构都是 byte-addressable 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 000,我们只会用其中的 30 位来表示对象的地址。

    最后将 isa = newisa,工作就结束了。

    方法、属性、协议

    在Objective-C中,对象的方法储存在类中,而非实例对象中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

    #if SUPPORT_INDEXED_ISA
    uint32_t index;
    #endif

    void setFlags(uint32_t set)
    {
    OSAtomicOr32Barrier(set, &flags);
    }

    void clearFlags(uint32_t clear)
    {
    OSAtomicXor32Barrier(clear, &flags);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear)
    {
    assert((set & clear) == 0);

    uint32_t oldf, newf;
    do {
    oldf = flags;
    newf = (oldf | set) & ~clear;
    } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 用于存储一个 Objective-C 类在编译期就已经确定的属性、方法以及遵循的协议
    struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    #ifdef __LP64__
    uint32_t reserved;
    #endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
    return baseMethodList;
    }
    };

    当一个对象的实例方法被调用时,需要先通过持有的isa指针查找相应的类,然后在类的class_data_bits_t 结构体中查找对应方法的实现(每个对象可以通过 cls->data()-> methods 来访问所属类的方法)。同时,每一个 objc_class 也有一个指向自己的父类的指针 super_class 用来查找继承的方法。

    而当一个类的类方法被调用时,通过类的isa在元类中获取方法的实现。如上图。

    结论

    isa用于查找对象(或类对象)所属类(或元类)的信息,比如方法列表、属性、协议等。

    ivar_t

    protocol_t

    method_t