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 中类的实质

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