Objective-C runtime - 类和对象

前言

OC是一门面向对象的语言,这篇文章先来看看OC的类和对象(Class && Object)

正文

  1. OC中的对象本质上是一个objc_object结构体
  2. OC中的类本质上是一个objc_class结构体
  3. id:指向objc_object的结构体指针
  4. Class:指向objc_class的结构体指针

objc_class 和 objc_object

先来看看两个结构体的结构(先只考虑结构体的成员变量,不考虑它的成员函数)

struct objc_object {
private:
    isa_t isa;
public:
    // function here
}

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;
      // method here
}

objc_object 定义在 objc-private.h 里面,该结构体只包含一个叫做 isa 的isa_t类型的变量

objc_class 定义在 objc-runtime-new.h 里面,它继承自objc_object,所以除了isa成员变量之外,它还有:

  1. 指向另一个objc_class结构体的指针 super_class;这一个objc_class包含了父类的信息
  2. 一个包含函数缓存的cache_t类型变量 cache
  3. 一个包含类的方法,属性等信息的class_data_bits_t类型的变量 bits

编译结束之后,OC的每个类都是以objc_class的结构体形式存在于内存之中,而且在内存中的位置已经固定。运行期间,创建新的对象的时候,也是创建objc_object的结构体

isa

成员变量isa包含了该对象,或者该类的信息。isa变量的类型是isa_t,定义在objc_private.h

union isa_t 
{
    Class cls;
    uintptr_t bits;

#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        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 deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)
    };
}

ps : 以上代码是在x86_64上的实现(Mac上面);iPhone(arm64等)上面的实现大同小异。具体实现可以参见runtime源码

has_assoc

表示该对象是否有关联对象

has_cxx_dtor

表示是否有析构函数

shiftcls

表示对象所属的类或者类所属的元类(meta class)的地址,也就是指向一个objc_class的指针,不过该指针只有44位。

64位系统的指针占用64bit的内存,但是使用整个指针大小来存储地址有点浪费。在mac的64位系统上面,使用47位作为指针,其他的17位用于其他目的(iPhone上面只使用33位)。又由于所有对象按照8字节对齐,所以指针都是能被8整除的,也就是后3bit均为0;所以类指针的实际有效位数为 47 - 3 = 44 位。这也是shiftcls只有44位的原因

isa初始化

在创建新的objc_object的时候,会使用objc_object的成员函数initIsa来初始化isa变量

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
{ 
    if (!indexed) {
        isa.cls = cls;
    } else {
        isa.bits = ISA_MAGIC_VALUE;
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

这里的indexed用来标记isa是否有结构体部分:

  1. 如果indexed为0,没有结构体部分,直接将对象所属的类的地址赋予cls变量
  2. 如果indexed为1,有结构体部分,先使用ISA_MAGIC_VALUE 给isa这个64位union初始化,然后在对64位上的每一位单独设置。比如设置是否含有析构函数(has_cxx_dtor),设置对象所属类的地址(shiftcls)。注意这里设置类地址的时候是将cls右移3位,再赋值给shiftcls的,原因就是类地址的最后3bit没有实际作用

isa()方法

该方法用来获取对象所属的类的指针,也就是表示该对象是一个什么类

inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}
// #define ISA_MASK 0x00007ffffffffff8ULL

返回的是一个64位的指向objc_class结构体的指针,其中的4-47bit为shiftcls的值,其他bit都是0

总结

OC的对象和类在内存中都是以结构体的形式存在的,类对应objc_class结构体,对象对应objc_object结构体。

在创建这两种结构体的过程中都会初始化isa变量。isa变量存放着对象所属的类的信息,或者类所属的元类的信息。关于元类的相关内容,可自行google。

另外,在创建objc_class的结构体的时候还会初始化相应的cache和bits变量,在后续的章节中将会说道

参考

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器