OC 中类的实质

作者: JasonWu | 来源:发表于2016-08-03 15:40 被阅读770次

id

id 可以表示任何 OC 中的对象,在 runtime 中对 id 是这么定义的

typedef struct objc_object *id;

所以 id 其实是一个指向 objc_object 结构体的指针类型,这也是为什么我们使用 OC 对象都需要添加 * ,而使用id对象的时候则不用。

既然 id 是指向结构体 objc_object的指针类型,那么 objc_object 是什么?
其实在 OC 中 objc_object 就是表示对象,所以在 OC 中任何对象都是 C 中的结构体。所以从这里看就知道为什么 id 可以表示任何对象类型了。

对象

在 OC 中对象是由结构体 objc_object 表示的,那么这个结构体的定义

struct objc_object {
private:
    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();

    // Optimized calls to retain/release methods
    id retain();
    void release();
    id autorelease();
};

为了方便观看其中省略了一些其他的东西。

可以看到结构体中除了一个变量 isa 就没有其他的变量了,还有一些其他常见的方法。所以其中的变量 isa 是什么东西呢?
其中 isaisa_t 类型的。

isa_t

找到 isa_t 的定义

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#if SUPPORT_NONPOINTER_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // indexed must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1;     // no r/r overrides
    // uintptr_t lock : 2;        // lock for atomic property, @synch
    // uintptr_t extraBytes : 1;  // allocated with extra bytes

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t indexed           : 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;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        uintptr_t indexed           : 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)
    };

# else
    // Available bits in isa field are architecture-specific.
#   error unknown architecture
# endif

// SUPPORT_NONPOINTER_ISA
#endif

};

可以看到 isa_t 是一个 union 类型,就是里面的成员都共用一个内存,大小则由最大的数据项决定。

那么为什么要用 union 类型呢?因为自从苹果推出了 iPhone5s 采用了 64 位架构的 A7 双核处理器,如果只用来存储一个地址的话就会十分的浪费内存,所以苹果引入了 TaggedPointerTaggedPointer 就是让本来存储内存地址的指针一部分用来存储其他的数据,从而减少浪费。而对于 isa_t 就是使用一部分存储所指向的类的地址,而另一部分则存储像引用计数等其他数据。

从上面的关于 isa_t 的结构中可以看出很多的宏就是判断是否支持 TaggedPoint 从而决定存储方式。而判断这个 isa 指针是否是 TaggedPoint 就是看指针的标志位是否为1。

#if SUPPORT_MSB_TAGGED_POINTERS
#   define TAG_MASK (1ULL<<63)
#else
#   define TAG_MASK 1
inline bool 
objc_object::isTaggedPointer() 
{
#if SUPPORT_TAGGED_POINTERS
    return ((uintptr_t)this & TAG_MASK);
#else
    return false;
#endif
}

所以回过头来看 isa_t 其中除了储存了一下相关数据

isa_t.png

Class

objc_object 结构体中有返回 Class 的函数

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

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

函数中返回的 Class 类型就是 OC 中的类

那么这个 Class 是什么,

typedef struct objc_class *Class;

可以看到 Class是一个指向结构体 objc_class 的指针,找到结构体 objc_class 的定义

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    //...
};

可以看到 objc_class 是继承自 objc_object 的,所以在 OC 中类其实也是一个对象。
所以在 objc_class 中有的成员是

  • isa : 从 objc_object 中继承得到
  • superclass : 指向父类
  • cache : 表示缓存的数据
  • bits : 里面存储了类的方法和成员变量等

当调用实例方法的时候,那么就从 objc_objectisa 中找到对象所属的类 ( objc_class )然后在所属的类中搜索相对应得方法。如果没有找到那么就根据 objc_classsuperclass 找到父类,在父类中搜索相对应的方法。

元类

我们上面提到,本质上 OC 中的类也是对象,它也是某个类的实例,这个类我们称之为元类(metaclass)。

因此,我们也可以通过调用类方法,比如 [NSObject new],给类对象发送消息。同样的,类对象能否响应这个消息也要通过 isa 找到类对象所属的类(元类)才能知道。也就是说,实例方法是保存在类中的,而类方法是保存在元类中的。

元类也是对象吗?是的话那它又是什么类的实例呢?是的,没错,元类也是对象(元类对象),元类也是某个类的实例,这个类我们称之为根元类(root metaclass)。不过,有一点比较特殊,那就是所有的元类所属的类都是同一个根元类(当然根元类也是元类,所以它所属的类也是根元类,即它本身)。根元类指的就是根类的元类,具体来说就是根类 NSObject 对应的元类*。

因此,理论上我们也可以给元类发送消息,但是 OC 倾向于隐藏元类,不想让大家知道元类的存在。元类是为了保持 OC 对象模型在设计上的完整性而引入的,比如用来保存类方法等,它主要是用来给编译器使用的。

可以用一张图概括

metaClass.png

相关文章

  • OC 中类的实质

    id id 可以表示任何 OC 中的对象,在 runtime 中对 id 是这么定义的 所以 id 其实是一个指向...

  • OC中类和对象的实质

    objc_object源码[https://opensource.apple.com//source/objc4/...

  • 随笔

    oc实质上是在c语言的基础上进行了一层oc包装,也可以说c语言就是oc的父类(超类),所有oc语言能实现的功能用c...

  • Runtime之理解OC中类与对象的实质

    在阅读Objective-C高级编程中block章节时,在第2章里讲到block的实质,作者写道,要理解block...

  • OC中的类

    第一个OC类 1.如何声明一个类 注意: 必须以@interface开头,@end结尾 成员变量的声明,必须写在@...

  • Objective-C 类与对象

    类方法 OC 中类的方法只有类的静态方法和类的实例方法 OC 中的方法只要声明在 @interface 里,就可以...

  • OC和Swift混编遇到的一些小麻烦

    一. OC中调用swift类中的方法时,编译器有时找不到方法声明 OC调用swift类中的方法,swift类需要有...

  • Objective-C 中类的数据结构

    一、类的结构 OC 中的代码在底层实现,使用的是 C、C++,所以要研究 OC 中的类结构,可以将 OC 的代码转...

  • 深入理解Objective-C中类的数据结构

    一、类的结构 OC 中的代码在底层实现,使用的是 C、C++,所以要研究 OC 中的类结构,可以将 OC 的代码转...

  • 从OC到Swift

    Swift调用OC类和方法 在工程名-Bridging-Header.h中添加需要调用OC类的头文件.h OC调用...

网友评论

  • 蔡建海:期待下篇
  • 蔡建海:赞,感觉没写完啊 想知道类创建时元类原理

本文标题:OC 中类的实质

本文链接:https://www.haomeiwen.com/subject/adwqsttx.html