“self.class” 和 “object_getClass” 为什么不同

“self.class” 和 “object_getClass” 为什么不同

最近看到很多人对 “self.class” 的解释是,”如果是实例的时候,返回类对象,否则返回自身。”

其实这句解释,稍微思考一下,会觉得说的含糊不清,这个类对象是什么?这个否则的情况又是如何的?

Objective-C 的面向对象

首先我简单介绍一下,在 Objective-C 中,所有的实例对象都通过实现对应的类来完成,类对象为实例推向提供了实例方法,实例属性的模板,可以说类是对象的模板。 那么又是什么来描述类对象呢? 元类。

Objective-C 通过元类来描述类对象,元类其实和类一样,只是用途不同,可以通过 class_isMetaClass() 来判断是否为元类。元类中存储了类方法的信息,元类是类的模板。

Objective-C 中,实例,类,元类,都是对象,都是结构体的形式存在,在逻辑上分别代表了 OOP 中的不同层级。

object_getClass 源码

下面我们看一下 object_getClass ,因为苹果已经开源 Objective-C 的大部分核心代码,我们看一眼就会明白了。

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

看起来 object_getClass 返回的是 isa 对象,isa 对象就是用来表明当前对象的模板是谁的。 (isa 对象其实并不是简单的一个指向 class 的对象,我们后面再谈。)
对于实例来说 isa 指向的应该是 Class 对象,对于 Class 对象来说,指向的是它的 MetaClass 对象,对于元类来说,isa 指向了元类的父类(另一个元类,层层向上,直到最后 元类 NSObject 的 isa 为 nil),

根据我们对 object_getClass(NSObject.class) 和 object_getClass(object.class) 的地址测试结果来看,他们的返回结果不同,

NSOBject.class 和 object.class 的地址相同。

我们再进行一次测试 class_isMetaClass(object_getClass([NSObject class])) ,结果为 true,而 class_isMetaClass(object_getClass([object class])) 为 false,证明了我的推断前者是元类,后者是类

从 object_getClass 的源码来看,它的实现对于类和实例来说都是一致的,说明它的返回值分别是元类和类,符合我们对 isa 的认知。

再看 self.class

反过来看所谓的 self.class ,其实它隐藏了一个事实,就是当 self 是一个 Class 的时候,它其实是 + [NSObject (Class)class] 方法,当 self 是实例的时候,它其实是 - [NSObject (Class)class]方法。根据 self 的不同情况, self.class 原本就是两个不同的方法,分别有着两种不同的实现而已,并不是一个 class 方法有不同的返回值。

同样经过地址测试,发现他们是一致的。说明前者指向的是自身,后者指向的是自己的类型对象。

好了,到这里已经真相大白了,关于对象的类型获取有三个方法,+ [NSObject class]- [NSObject class]object_getClass()
只有 + [NSObject class] 会返回类对象自身,其他方法都会返回对象的 isa 对象(类对象)。

关于 isa

objc 源码 objc-private.h ,苹果并没有公布它的实现细节

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();

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*indexed=false*/);
    void initClassIsa(Class cls /*indexed=maybe*/);
    void initProtocolIsa(Class cls /*indexed=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

从这里可以看到真正的 isa 是一个 isa_t 结构体,并且根据不同的 objective-c 对象,提供了不同的初始化 Isa 函数。说明 isa 针对不同情况也提供了不同的分装实现。

写到这里我想说的是,透过现象看到本质是认清事物的唯一方式。