为什么要存在MetaClass

iOS优化

最近去外面面试了一次,有一个问题觉得很有意思:OC中为什么要存在metaClass,在类的结构上这么设计的原因是什么?metaClass中都有什么?有什么优点?

这篇文章我们就来讨论一下上面的这几个问题!

了解 MetaClass

MetaClass结构

下面我们先来看看这个类的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

metaClass 也是objc_class 因此结构大概也是这样的!,不过就目前所知到的metaClass是用来存放类方法的! 因为OC中没有类属性因此 objc_ivar_list 为空!

MetaClass的创建

首先了解一点,在OC中每一个类都有一个对应的MetaClass! 因此 我们在创建一个类的时候,就会一起创建这个类的元类。

因此,我们可以看一下当我们动态创建一个类的时候 运行时实际上都做了什么!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Class objc_allocateClassPair(Class superclass, const char *name, 
size_t extraBytes)
{
Class cls, meta;

rwlock_writer_t lock(runtimeLock);

// Fail if the class name is in use.
// Fail if the superclass isn't kosher.
if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) {
return nil;
}

// Allocate new classes.
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);

// fixme mangle the name if it looks swift-y?
objc_initializeClassPair_internal(superclass, name, cls, meta);

return cls;
}

从上面的代码中我们可以很明显的看出:

1
2
cls  = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);

clsmeta的创建方法和参数完全一致!

怎么获取一个类的MetaClass

objc_getMetaClass(const char * _Nonnull name)

我们来看一下这个方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class objc_getMetaClass(const char *aClassName)
{
Class cls;

if (!aClassName) return Nil;

cls = objc_getClass (aClassName);
if (!cls)
{
_objc_inform ("class `%s' not linked into application", aClassName);
return Nil;
}

return cls->ISA();
}

先是使用objc_getClass获取类名对应的类 然后直接利用cls->ISA()获取这个类对应的元类

判断一个类是否为元类

bool class_isMetaClass(Class _Nullable cls)

1
2
3
4
5
6
7
8
9
10
11
12

BOOL class_isMetaClass(Class cls)
{
if (!cls) return NO;
return cls->isMetaClass();
}

bool isMetaClass() {
assert(this);
assert(isRealized());
return data()->ro->flags & RO_META;
}

从上面的代码我们可以看出 判断是否为元类的条件是 data()->ro->flags & RO_META。那么这个类的flags是什么时候设置的呢?

找了好久,我们在objc_initializeClassPair_internal方法中找到了下面这段代码

1
2
3
4
5
6
cls_ro_w->flags = 0;
meta_ro_w->flags = RO_META;
if (!superclass) {
cls_ro_w->flags |= RO_ROOT;
meta_ro_w->flags |= RO_ROOT;
}

那么这个方法什么时候被调用呢? 追踪了一下我们发现 是在objc_initializeClassPair_internal这个方法中被调用的! 这个方法是不是很熟悉呢! 他就是objc_allocateClassPair中的最后一句代码。

MetaClass 存在的意义是什么呢?

实际上如果没有元类的存在,而是在class中增加一个classmethodlist用来存放这个类的类方法,也是可以达到我们想要实现的目的。

那么MetaClass存在的意义到底是什么呢?

为了更好的了解MetaClass存在的原因,我们先看一下他的作用:

先看一下下面这张图片:

metaclass

一切皆对象的思想

我们都知道对象里有一个isa指针

ISA指针 实际上就是 is a 的缩写。表示这个对象是一个什么

从上面的图上我们也可以看出最顶层的RootMetaClass的isa指向知己superClass指向RootClass,这就形成了一个闭环。

加入 我们将元类去掉,那么我们类的ISA指针应该指向什么呢?如果没办法指向一个RootMetaClass那表明这个类是一个什么类型的对象呢?

当然这个是根据结论反推原因,是不太合逻辑的,但是 更容易被我们理解。

从Smalltalk重新认识面向对象

以前谈到面向对象,总会提到,面向对象三特征:封装、继承、多态。但其实,面向对象中也分流派,如C++这种来自Simula的设计思想的,更注重的是类的划分,因为方法调用是静态的。而如Objective-C这种借鉴Smalltalk的,更注重的是消息传递,是动态响应消息。

而面向对象三种特征,更基于的是类的划分而提出的。

这两种思想最大的不同,我认为是自上而下和自下而上的思考方式。

  • 类的划分,要求类的设计者是以一个很高的层次去设计这个类,提取出类的特性和本质,进行类的构建。知道类型才可以去发送消息给对象
  • 消息传递,要求的是类的设计者以消息为起点去构建类,也就是对外界的变化进行响应,而不关心自身的类型,设计接口。尝试理解消息,无法处理则进行特殊处理。

消息传递对于面向对象的设计,其实在于给出一种对消息的解决方案。而面向对象优点之一的复用,在这种设计里,更多在于复用解决方案,而不是单纯的类本身。这种思想就如设计组件一般,关心接口,关心组合而非类本身。其实之所以有MetaClass这种设计,我的理解并不是先有MetaClass,而是在万物都是对象的Smalltalk里,向对象发送消息的基本解决方案是统一的,希望复用的。而实例和类之间用的这一套通过isa指针指向的Class单例中存储方法列表和查询方法的解决方案的流程,是应该在类上复用的,而MetaClass就顺理成章出现罢了。

上面这部分是摘自:Why is MetaClass in Objective-C

维基百科的解释

1
2
3
4
5
6
7
8
9
10
11
12
13

Metaclasses in Objective-C are almost the same as those in Smalltalk-80—not surprising since Objective-C borrows a lot from Smalltalk. Like Smalltalk, in Objective-C, the instance variables and methods are defined by an object's class. A class is an object, hence it is an instance of a metaclass.(OC中的Metaclasses 基本上和Smalltalk-80相同,鉴于OC从Smalltalk中借鉴了很多因此这并不令人干到奇怪。类似Smalltalk 在OC中实例的变量和方法被定义在对象的类中,class也是一个对象,于是class是mataclass的一个实例。)

Like Smalltalk, in Objective-C, class methods are simply methods called on the class object, hence a class's class methods must be defined as instance methods in its metaclass. Because different classes can have different sets of class methods, each class must have its own separate metaclass. Classes and metaclasses are always created as a pair: the runtime has functions objc_allocateClassPair() and objc_registerClassPair() to create and register class-metaclass pairs, respectively.(类似Smalltalk,在OC中类方法通过类对象调用,于是一个类的类方法必须在metaclass中以实例方法的形式定义。因为不同的类可以后不同的类方法集合,每一个类必须有自己独立的metaClass。Class和class-metaClass一起被创建和注册)

There are no names for the metaclasses; however, a pointer to any class object can be referred to with the generic type Class (similar to the type id being used for a pointer to any object). (metaClass没有名字,然而,指向任何类对象的指针可以用泛型类型引用)(类似id可以指向所有的对象)

Because class methods are inherited through inheritance, like Smalltalk, metaclasses must follow an inheritance scheme paralleling that of classes (e.g. if class A's parent class is class B, then A's metaclass's parent class is B's metaclass), except that of the root class.(因为,类方法通过继承获取,类似Smalltalk,元类必须遵循与类类似的继承方案

Unlike Smalltalk, the metaclass of the root class inherits from the root class (usually NSObject using the Cocoa framework) itself. This ensures that all class objects are ultimately instances of the root class, so that you can use the instance methods of the root class, usually useful utility methods for objects, on class objects themselves.(与Smalltalk不同,根类的metaclass继承自根类本身,这就确保了所有的类对象都是根类的对象。以便您可以使用根类的实例方法,通常是对象的有用实用工具方法,以及类对象本身。)

Since metaclass objects do not behave differently (you cannot add class methods for a metaclass, so metaclass objects all have the same methods), they are all instances of the same class—the metaclass of the root class (unlike Smalltalk). Thus, the metaclass of the root class is an instance of itself. The reason for this is that all metaclasses inherit from root class; hence, they must inherit the class methods of the root class.
(由于元类对象行为不同(你不能为元类添加类方法,所以元类对象都有相同的方法),它们都是同一类的实例 - 根类的元类(与Smalltalk不同)因此,根类的元类是它自己的一个实例。原因是所有元类都继承自根类;因此,他们必须继承根类的类方法。)

最后

回到一开始那个问题,为什么要设计MetaClass,去掉把类方法放到类里面行不行?

我的理解是,可以,但不Smalltalk。这样的设计是C++那种自上而下的设计方式,类方法也是类的一种特征描述。而Smalltalk的精髓正在于消息传递,复用消息传递才是根本目的,而MetaClass只不过是因此需要的一个工具罢了。

参考文章

Why is MetaClass in Objective-C
function/bind的救赎(上)