iOS八股文(三)类对象的结构(上)

1,516 阅读3分钟

对象的分类

Object- C中所有对象可以分为3类,实例对象,类对象,元类对象。其中我们开发者常用的继承自NSObject都属于实例对象,实例对象通过isa指针指向的是类对象。类对象通过isa指向的是元类对象。类对象和元类对象拥有同样的结构,都是来自objc_class

objc_class结构

objc4源码中查找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
}

其中isa指针式其继承自objc_object的。本文主要讲解类结构中的isasuperclassbitscache的内容单放一篇

isa在之前的内容里面已经讲过,主要是指向类对象或者元类对象的。 superclass是体现出继承关系的。 cache是方法缓存。 bits是类的其他信息,例如成员变量,方法列表,协议,属性。

注意:元类对象结构也是如此,但元类对象里面没有成员变量,协议,属性这些内容。

isa指向图

相信做iOS开发都必须掌握的一个图。

2c26c9077c7d4667a478bb6164c9150c~tplv-k3u1fbpfcp-zoom-in-crop-mark-1304-0-0-0.image.png

这里对isa指向做一个简单的总结

实例对象的isa指针指向对应的类对象,类对象的isa指针指向对应的元类对象,元类对象的isa指向基类的元类对象(根元类)。

可以简单的用lldb调试来验证一下。

- (void)test {
    // 
    NSObject *objc = [[NSObject alloc] init];
    // isa指向的就是类对象
    Class objcClass1 = [NSObject class];

    Class objcMetaClass = object_getClass([NSObject class]);
}

image.png 先获取objcisa指针的值,然后用这个值与运算ISA_MASK,得到的值,正好是类对象的地址。

image.png 然后用同样的方法获取类对象的值,和ISA_MASK与运算得到元类对象的地址。

可以继续这样再操作一轮,得到的还是元类对象的地址。这是因为该类是NSObjectNSObject元类对象的isa是指向自己的(图中右上交的循环箭头)。

image.png 这里因为0x00000001d64226c0的高9位和低3位都是0所以没有做与运算,结果其实是一样的。 注意ISA_MASK的使用需要注意环境,我这里是M1的模拟器。

superClass指向

superClass的指向也在上面那个经典的图中。这里要注意superclassobjc_class才有的,所以实例对象是没有的。类对象的superclass指向父类对象,元类对象的superclass指向父类的元类对象,根元类对象的superclass指针指向 根类对象。根类对象的superclassnil

注意:根元类对象的superclass指针指向 根类对象,这个是有一个面试题的。 请听题:

@interface NSObject (Interview)
+ (void)foo;
@end
@implementation NSObject (Interview)
//能通过这个调用的原因是,NSObject的基元类的supclass为NSobject的元类
- (void)foo {
   NSLog(@"IMP: -[NSObject foo]");
}
@end
- (void)test {
    [NSObject foo];
}

上面的代码能执行成功么?这题要结合objc_msgSend一起看,考察的点就是根元类对象的superclass指针指向根类对象。

bits

类的结构中还有一个bits,里面也存了很多信息,我们先看bits的数据结构 class_data_bits_t

struct class_data_bits_t {
    friend objc_class;
    // Values are the FAST_ flags above.
    uintptr_t bits;
    public:
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }
}

其实最最重要的是其中的2个方法。datasafe_ro,两个方法分别返回class_rw_tclass_ro_t

这里ro_t的获取也是通过data方法获取的,所有可以理解为ro_t也在rw_t之中。

class_rw_t

继续来看class_rw_t的数据结构。

image.pngrw_t中可以 看到有这3个方法,通过这三个方法分别能获取到类的方法、属性、协议。

class_ro_t

再看class_ro_t的数据结构。

image.png

image.png

可以看到有方法、属性、协议和成员变量。但方法、属性、协议的命名都是base开通的。