美文网首页傲视苍穹iOS《Objective-C》VIP专题Programming Language
OC对象的本质<二> 实例对象,类对象,元类对象

OC对象的本质<二> 实例对象,类对象,元类对象

作者: 雪山飞狐_91ae | 来源:发表于2018-07-01 22:30 被阅读24次

    OC对象的本质<一>

    OC对象的分类

    OC对象可以分为三类,分别是实例对象,类对象,元类对象。

    实例对象(instance对象)

    instance对象是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。

    • instance对象在内存中存储的信息
      isa指针(因为几乎所有的对象都继承自NSObject,而NSObject对象的结构体重就是一个isa指针)
      其他成员变量(注意这里存储的是成员变量的值)
      我们看一下Demo:
    @interface Person:NSObject
    {
        @public
        int _age;
    }
    @property (nonatomic, assign)int height;
    @end
    
    @implementation Person
    @end
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            
            Person *p1 = [[Person alloc] init];
            p1->_age = 3;
            
            Person *p2 = [[Person alloc] init];
            p2->_age = 4;
            
            return 0;
        }
    }
    

    那么这个时候首先为p1实例对象分配存储空间,先存储isa这个指针,然后存储成员变量_age=3;对于p2实例对象也是一样的。由于p1指针指向Person实例对象,也就是指向Person实例对象在内存中的首地址,而Person实例对象中存储的第一个成员变量是isa指针,所以p1指针指向的地址就是存储isa指针的地址。


    17703D99-4D1C-45D2-85C2-5E053C2FA656.png

    类对象(Class对象)

    类对象的获取方式:

    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];
    Class objectClass1 = [object1 class];
    Class objectClass2 = [object2 class];
    Class objectClass3 = object_getClass(object1);
    Class objectClass4 = object_getClass(object2);
    Class objectClass5 = [NSObject class];
    

    一个类的类对象在内存中是唯一的,这就说明我们通过这五种方式所创建的类方法是同一个对象。我们通过打印这5个类对象的内存地址来验证一下:

    NSLog(@"%p \n %p \n %p \n %p \n %p \n", objectClass1, objectClass2, objectClass3, objectClass4, objectClass5);
    

    打印结果:

     0x10b0a6ea8 
     0x10b0a6ea8 
     0x10b0a6ea8 
     0x10b0a6ea8 
     0x10b0a6ea8
    

    这也就验证了这五个对象是同一个对象,也就是一个类只有一个类对象。
    既然每个类的类对象只有唯一一个,那么在每个类的类对象中会存储什么东西呢?肯定是存放那么只需要存储一份的东西,不会是像成员变量的值一样,每个实例对象都可以有不同的成员变量值。

    • Class对象在内存中存储的信息主要包括:
      isa指针
      superclass指针
      类的属性信息(@property),类的对象方法信息(instance method)
      类的协议信息(@protocol),类的成员变量信息(ivars,类型,名称等)


      类对象包含的信息.png

    元类对象

    • 元类对象的获取方法
    //我们在object_getClass()方法中传入类对象就得到了元类对象,每个类的元类对象只有一个,所以objectMetaClass1和objectMetaClass2是同一个对象
     Class objectMetaClass1 = object_getClass([NSObject class]);
     Class objectMetaClass2 = object_getClass(objectClass1);
    

    那么元类对象中存放的是什么信息呢?大家想一下实例对象和元类对象还有什么信息漏了就能明白元类信息中包含什么信息了。

    • meta-Class对象中包含的信息
      isa指针
      superclass指针
      类的类方法信息


      元类对象包含的信息.png
    • class_isMetaClass()
      class_isMetaClass()判断传进去的对象是否是元类对象。
      BOOL result = class_isMetaClass(objectMetaClass1);

    isa指针

    我们先看一个Person类:

    @interface Person:NSObject<NSCopying>
    {
        @public
        int _age;
    }
    @property (nonatomic, assign)int no;
    - (void)personInstanceMethod;
    + (void)personClassMethod;
    @end
    
    @implementation Person
    - (void)personInstanceMethod{
        
        
    }
    + (void)personClassMethod{
        
        
    }
    
    - (id)copyWithZone:(NSZone *)zone{
        
        return nil;
    }
    

    这个Person类有成员变量,属性,有遵守的协议,实例方法,类方法。首先我们通过实例对象调用实例方法:

    Person *person = [[Person alloc] init];
    [person personClassMethod];
    

    [Person personClassMethod]这句代码在底层的实现一定是objc_msgSend(person,@selector(personInstanceMethod))。这里就有一个问题了,我们是给实例对象发消息,调用实例方法,可以实例对象中没有实例方法的信息呀。同样的,当我们调用类对象的时候[Person personInstanceMethod]这句话在底层的实现时一定是转化为objc_msgSend([Person class],@selector(personClassMethod))这样,也就是给一个类对象发送消息,调用类方法。但是类方法是在元类对象里面,不在类对象中呀,这是怎么调用的呢?这时isa指针就派上用场了。
    Person实例对象的isa指针指向Person类对象,Person类对象的isa指针指向Person元类对象。

    • 当我们调用对象方法时,首先通过实例对象中的isa指针找到类对象,然后获得类对象中的实例方法信息。
    • 当我们调用类方法时,首先通过类对象的isa指针找到元类对象,再找到元类对象中的类方法信息,实现调用。

    superclass指针

    类对象的superclass指针

    我们创建一个子类Student类继承自Person类。

    @interface Student:Person <NSCoding>
    {
        @public
        int _weight;
    }
    @property (nonatomic, assign)int height;
    - (void)studentInstanceMethod;
    + (void)studentClassMethod;
    
    @end
    
    @implementation Student
    - (void)studentInstanceMethod{
        
    }
    
    + (void)studentClassMethod{
        
    }
    - (id)initWithCoder:(NSCoder *)aDecoder{
        
        return nil;
    }
    - (void)encodeWithCoder:(NSCoder *)aCoder{
        
    }
    @end
    
    ![![85F2CFF1-F8EF-438A-B32D-CF65BCF10A5A.png](https://img.haomeiwen.com/i5796542/fdab2d71d7b42f54.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ](https://img.haomeiwen.com/i5796542/7e9a426e8fb01d06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    我们看一下student实例对象调用实例方法:

    Student *student = [[Student alloc] init];
    [student studentInstanceMethod];
    

    这个过程我们已经很清楚了,就是通过student实例对象的isa指针来找到Student的类对象,然后在类对象中找到实例方法,完成调用。那么如果student实例对象调用的是父类Person类的实例方法呢?[student personInstanceMethod];这个调用过程又是怎样的呢?- (void)personInstanceMethod这个类方法肯定是存放在Person类的类方法里面的。这个时候就是superclass指针发挥作用的时候了。
    student实例对象首先通过其isa指针找到自己的类对象,然后Student类对象查找自己有没有存储- (void)personInstanceMethod这个实例方法,发现自己并没有这个实例方法的信息,于是就通过自己的superclass指针来找到父类的类对象也就是Person类对象,Person类对象查看自己有没有存储- (void)personInstanceMethod这个实例方法的信息,结果找到了这个实例方法的信息,至此student实例对象(^-^)也就获取了- (void)personInstanceMethod实例方法的信息。
    类对象的superclass指针指向父类的类对象

    元类对象的superclass指针

    85F2CFF1-F8EF-438A-B32D-CF65BCF10A5A.png

    首先我们来看Student类对象调用自己的类方法:

    [Student studentClassMethod];
    

    这个调用过程应该已经很清楚了,Student类的类方法信息是存储在元类对象中的。Student类对象首先通过自己的isa指针找到Student元类对象,Student元类对象查看自己有没有studentClassMethod这个类方法的信息,查看后发现有,就传给类对象。那么如果要调用父类Person类的类方法呢?

    [Student personClassMethod];
    

    首先Student类对象通过自己的isa指针找到Student元类对象,Student元类对象查看自己有没有personClassMethod这个类方法的信息,查找后没有就利用自己的superclass指针找到父类的元类对象,也就是Person类的元类对象,Person类的元类对象查看后发现自己有personClassMethod这个类方法的信息,至此Student类对象就找到了personClassMethod这个类方法的信息。
    元类对象的superclass指针指向父类的元类对象。

    isa,superclass总结

    放上一张经典的总结图

    630FEC40-DF9E-4755-80CA-D65810CB3DF1.png
    总结起来就是:
    实例对象的isa指针指向该类的类对象。
    类对象的isa指针该类的元类对象。
    元类对象的isa指针指向基类的元类对象。
    类对象的superclass指针指向父类的类对象
    元类对象的superclass指向父类的元类对象(基类除外)
    基类的元类对象的superclass指向基类的类对象。

    isa指针的一个小细节

    前面已经说了实例对象的isa指针指向类对象,类对象的isa指向元类对象。所以实例对象的isa指针的值就是类对象的地址值,类对象的isa指针的值就是元类对象的地址值。我们打印看一下结果:

    Person *person = [[Person alloc] init];
    Class personClass = [Person class];
    Class personMetaClass = object_getClass(personClass);
    NSLog(@"%p, %p, %p", person, personClass, personMetaClass);
    

    打印结果:

    Student[1372:63404] 0x600000008390, 0x10e7a4130, 0x10e7a4108
    

    然后我们再打个断点,查看一下各个对象的isa指针值。

    1697CA48-0D78-4273-9B39-86D3581DF4DA.png
    我们在调试框中输入p person->isa发现打印的是$0 = Person,这并不是我们想要的,我们输入p (long)person->is,这下打印出想要的结果了$1 = 4537860400,但是这是10进制表示,我们需要转化为16进制,再输入p/x (long)person->isa,打印得到16进制结果:$2 = 0x000000010e7a4130。这也就验证了实例对象的isa指针指向类对象。
    然后我们试着打印类对象的isa指针的值:
    p/x personClass->isa
    打印结果是:
    error: member reference base type 'Class' is not a structure or union所以这条路行不通了。
    我们已经知道了这个personClass这个类对象中第一个成员变量一定是isa指针,所以我们可以自己创建一个结构体:
    struct pd_objc_class{
        
        Class isa
    };
    

    然后我们把personClass这个类对象强制转化成pd_objc_class结构体类型:

     struct pd_objc_class *personClass2 = (__bridge struct pd_objc_class *)(personClass);
    

    然后再通过p/x personClass2->isa打印isa指针的值:
    $0 = 0x000000010583f108,同时我们从一开始的打印结果可以找到personMetaClass对象的地址为0x000000010583f108。
    这样也就证明了类对象的isa指针是指向元类对象的。

    验证实例对象,类对象,元类对象的结构

    类对象和元类对象的类型都是Class类型,所以本质上来讲它们的结构是一致的。那么我们只需要搞清楚这个Class类型的结构就可以搞清楚类对象和元类对象的结构了。
    按住command点击Class查看结构:

    typedef struct objc_class *Class;
    

    可以看到这是一个obkc_class类型的结构体指针。然后我们继续点击进objc_class查看:

    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;
    

    但是我们看到这个是条件编译,#if !OBJC2也就是如果不是OBJC2就编译,但是现在就是OBJC2,所以这段条件代码不会编译。因此这个代码就不足以作为参考。那么我们只好从源码中查看objc_class的结构。
    我们在源码中搜索objc_class,从objc-runtime-new.h中找到了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,我们点进objc_object结构体中看看,可以看到

    struct objc_object {
    private:
        isa_t isa;
    }
    

    里面只有一个成员变量isa指针。所以objc_class这个结构体的结构就等价于:

       struct objc_class : objc_object {
        void *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();
        }
    }
    

    第一个成员变量是isa指针,第二个是superclass指针。第三个cache是和方法的缓存有关的。第四个bits先不管。第五个是一个方法,返回值是class_rw_t类型的,我们点进class_rw_t看看它的结构:

    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_ro_t看看它的结构:

    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;
        }
    };
    

    从这个角度确实可以证明objc_class的结构中有isa指针,superclass指针,方法列表,属性列表,成员变量列表,协议列表等。


    2874E11F-27F3-4E2B-8276-443CC9CD3A24.png

    通过转化为C++的源码来证实实例对象,类对象,元类对象的结构

    类对象

    @interface Person:NSObject<NSCopying>
    {
        @public
        int _age;
    }
    @property (nonatomic, assign)int no;
    - (void)personInstanceMethod;
    + (void)personClassMethod;
    @end
    
    @implementation Person
    - (void)personInstanceMethod{  
    }
    + (void)personClassMethod{   
    }
    - (id)copyWithZone:(NSZone *)zone{
        return nil;
    }
    
    @end
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            
            Person *person = [[Person alloc] init];
            person->_age = 10;
            person.no = 3;
            Class personClass = [Person class];
            Class personMetaClass = object_getClass(personClass);
     
            return 0;
        }
    }
    

    我们把person类的代码转为C++的源码:
    我们找到class_t类型的OBJC_CLASS$_Person这个结构体,这个结构体就死类对象的结构。

    struct _class_t OBJC_CLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
        0, // &OBJC_METACLASS_$_Person,
        0, // &OBJC_CLASS_$_NSObject,
        0, // (void *)&_objc_empty_cache,
        0, // unused, was (void *)&_objc_empty_vtable,
        &_OBJC_CLASS_RO_$_Person,
    

    我们找到_class_t这个结构体,查看结构

    struct _class_t {
        struct _class_t *isa;
        struct _class_t *superclass;
        void *cache;
        void *vtable;
        struct _class_ro_t *ro;
    };
    

    我们看到其中第一个成员是isa指针,第二个成员是superclass指针,cache和vtable我们先不管。接着我们看到_class_ro_t这个结构体:

    struct _class_ro_t {
        unsigned int flags;
        unsigned int instanceStart;
        unsigned int instanceSize;
        unsigned int reserved;
        const unsigned char *ivarLayout;
        const char *name;
        const struct _method_list_t *baseMethods;
        const struct _objc_protocol_list *baseProtocols;
        const struct _ivar_list_t *ivars;
        const unsigned char *weakIvarLayout;
        const struct _prop_list_t *properties;
    };
    

    这个结构体我们前面见多过,应该已经比较熟悉了。然后我们找到类对象中这个结构的实现:

    static struct _class_ro_t _OBJC_CLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        0, 
        __OFFSETOFIVAR__(struct Person, _age), 
        sizeof(struct Person_IMPL), 
        (unsigned int)0, 
        0, 
        "Person",
        (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Person,
        (const struct _objc_protocol_list *)&_OBJC_CLASS_PROTOCOLS_$_Person,
        (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Person,
        0, 
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person,
    };
    
    • 我们可以看到instanceSize对应的是sizeof(struct Person_IMPL)。
    • name也就是类名,对应的是"Person"。
    • baseMethods对应的是OBJC$_INSTANCE_METHODS_Person这个结构体,我们找到这个结构体的实现:
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[4];
    } _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        4,
        {{(struct objc_selector *)"personInstanceMethod", "v16@0:8", (void *)_I_Person_personInstanceMethod},
        {(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_Person_copyWithZone_},
        {(struct objc_selector *)"no", "i16@0:8", (void *)_I_Person_no},
        {(struct objc_selector *)"setNo:", "v20@0:8i16", (void *)_I_Person_setNo_}}
    };
    

    我们通过INSTANCE_METHODS这个名字知道这个里面存放的是实例方法,通过其初始化可以看到,有四个实例方法,方法名分别是"personInstanceMethod";"copyWithZone:";"no";"setNo:"。这是符合实际的。

    • baseProtocols根据名字应该是存放的协议信息,它是用OBJC_CLASS_PROTOCOLS$_Person这个结构体初始化的。我们找到这个结构体的实现:
    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[1];
    } _OBJC_CLASS_PROTOCOLS_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1,
        &_OBJC_PROTOCOL_NSCopying
    };
    

    可以看到,协议数量是1,协议是_OBJC_PROTOCOL_NSCopying,通过名称我们得知是NSCopying,这里不再展开。

    • ivars是变量的意思,其对应的是OBJC$_INSTANCE_VARIABLES_Person这个结构体,我们查看一下这个结构体的结构:
    static struct /*_ivar_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count;
        struct _ivar_t ivar_list[2];
    } _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_ivar_t),
        2,
        {{(unsigned long int *)&OBJC_IVAR_$_Person$_age, "_age", "i", 2, 4},
         {(unsigned long int *)&OBJC_IVAR_$_Person$_no, "_no", "i", 2, 4}}
    };
    

    可以看到它有两个成员变量,其中一个是_age,类型是int,大小是4字节,还有一个成员变量是_no,类型是int,大小是4字节。

    • properties是属性,可以猜测里面存储的是属性信息。我们看到其对应的结构体是_PROP_LIST_Person,查看一下其实现:
    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[1];
    } _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"no","Ti,N,V_no"}}
    };
    

    我们可以看到有一个属性,属性名是no。

    元类对象

    我们找到class_t类型的结构体OBJC_METACLASS$_Person,这个结构体就是元类对象的实现:

    struct _class_t OBJC_METACLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
        0, // &OBJC_METACLASS_$_NSObject,
        0, // &OBJC_METACLASS_$_NSObject,
        0, // (void *)&_objc_empty_cache,
        0, // unused, was (void *)&_objc_empty_vtable,
        &_OBJC_METACLASS_RO_$_Person,
    

    我们找到OBJC_METACLASS_RO$_Person这个结构体

    static struct _class_ro_t _OBJC_METACLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1, 
        sizeof(struct _class_t), 
        sizeof(struct _class_t), 
        (unsigned int)0, 
        0, 
        "Person",
        (const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_Person,
        0, 
        0, 
        0, 
        0, 
    };
    

    我们通过这个名称可以看出这是为初始化元类对象的。
    还是贴一下_class_ro_t的结构:

    struct _class_ro_t {
        unsigned int flags;
        unsigned int instanceStart;
        unsigned int instanceSize;
        unsigned int reserved;
        const unsigned char *ivarLayout;
        const char *name;
        const struct _method_list_t *baseMethods;
        const struct _objc_protocol_list *baseProtocols;
        const struct _ivar_list_t *ivars;
        const unsigned char *weakIvarLayout;
        const struct _prop_list_t *properties;
    };
    

    对比来看,OBJC_METACLASS_RO_$_Person这个结构体的初始化就要简单很多。

    • name就是类名,是"Person"。
    • baseMethods存放的是方法,其是用OBJC$_CLASS_METHODS_Person这个结构体初始化的,我们查看一下这个结构体的结构:
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[1];
    } _OBJC_$_CLASS_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"personClassMethod", "v16@0:8", (void *)_C_Person_personClassMethod}}
    };
    

    类方法只有一个,方法名是“personClassMethod”。
    其他的如baseProtocols,ivars,properties这些都是空的。
    这也就证实了元类对象中只有isa指针,superclass指针,还有类方法。

    相关文章

      网友评论

      本文标题:OC对象的本质<二> 实例对象,类对象,元类对象

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