OC对象的本质 实例对象,类对象,元类对象

栏目: Objective-C · 发布时间: 6年前

内容简介:OC对象的分类OC对象可以分为三类,分别是实例对象,类对象,元类对象。

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指针的地址。

OC对象的本质 实例对象,类对象,元类对象

类对象(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,类型,名称等)

OC对象的本质 实例对象,类对象,元类对象

元类对象

  • 元类对象的获取方法

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

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

  • meta-Class对象中包含的信息

    isa指针

    superclass指针

    类的类方法信息

OC对象的本质 实例对象,类对象,元类对象

  • 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;
}
</nscopying>

这个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
</nscoding>

OC对象的本质 实例对象,类对象,元类对象

我们看一下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指针

OC对象的本质 实例对象,类对象,元类对象

首先我们来看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总结

放上一张经典的总结图

OC对象的本质 实例对象,类对象,元类对象

总结起来就是:

实例对象的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指针值。

OC对象的本质 实例对象,类对象,元类对象

我们在调试框中输入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指针,方法列表,属性列表,成员变量列表,协议列表等。

OC对象的本质 实例对象,类对象,元类对象

通过转化为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;
    }
}
</nscopying>

我们把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指针,还有类方法。

作者:雪山飞狐_91ae

链接: https://www.jianshu.com/p/80d665c25d38


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

CSS高效开发实战—CSS 3、LESS、SASS、Bootstrap、Foundation

CSS高效开发实战—CSS 3、LESS、SASS、Bootstrap、Foundation

谢郁 / 电子工业出版社 / 2014-9 / 59.00

想象一下,一个网页只有HTML,没有CSS,那就是素颜和上妆的区别。而一个网页只有CSS,没用CSS 3,那就是马车和汽车的区别!汽车代表的是高效、美观,CSS 3的意图也是如此。移动设备的流行导致了响应式设计的流行,而CSS 3正是实现这种设计的精髓。《CSS高效开发实战—CSS 3、LESS、SASS、Bootstrap、Foundation》围绕的就是如何跨浏览器、跨设备进行高效率的CSS开......一起来看看 《CSS高效开发实战—CSS 3、LESS、SASS、Bootstrap、Foundation》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换