Objc源码分析-runtime-Class
看了一段时间objc的源码,准备开个大坑,写一下了解到的东西,这一时半会是完工不了了,一篇一篇慢慢写吧。
先看一下官方对objc的介绍。
Objective-C is the primary programming language you use when writing software for OS X and iOS. It’s a superset of the C programming language and provides object-oriented capabilities and a dynamic runtime. Objective-C inherits the syntax, primitive types, and flow control statements of C and adds syntax for defining classes and methods. It also adds language-level support for object graph management and object literals while providing dynamic typing and binding, deferring many responsibilities until runtime.
(Objective-C是您在为OS X和iOS编写软件时使用的主要编程语言。 它是C编程语言的超集,提供面向对象的功能和动态运行时。 Objective-C继承了C的语法,原始类型和流控制语句,并添加了用于定义类和方法的语法。 它还为对象图管理和对象文字添加语言级支持,同时提供动态类型和绑定,将许多职责推迟到运行时。)
显然运行时是objc的这个语言非常重要的特性,它可以把很多静态语言(显然objc是动态语言)在编译和链接时期做的事情推迟到运行时期在做。这样写代码就有很大的灵活性,比如把消息转发给我们想要的对象,或者随意交换一个方法的实现等。
所以说objc除了编译器之外,还需要一个运行时系统 (runtime system) 来执行编译后的代码。Objective-C Runtime 就是这个运行时系统了,这个库使得C有了面向对象的能力。
下面我们就分析一下runtime是如何做到的。
由于日常接触的最多的是类与对象,所以我准备就从这里开始写起。
Class
OC的对象都是需要继承NSObject的,那么我们先看看NSObject。
|
|
发现除了常用了一些objc的方法外,有个Class类的isa指针。Class类在平时使用的时候会多多少少遇到过,比如说判断某对象是不是某类的实例。
|
|
通过看源码我们可以看到其实现。
|
|
由以上代码可以看出,其实判断OC对象的类型,就是判断类型为Class的对象是否相等,那么Class到底是什么呢。
|
|
可以看到Class其实就是指向objc_class结构体的指针(又被称为类对象)。
objc_class结构体里主要的成员如下。
|
|
现在我们可以说isMemberOfClass方法其实就是判断两个结构体(objc_class)实例的指针地址是否相同。
objc_class 又继承于objc_object,那么我们再看看objc_object的主要结构。
|
|
看objc_object的名字也可以猜到这个应该和NSObject有关系,那么他们是怎么产生联系的呢?
在 clang -rewrite-objc
(把oc代码转写成c/c++代码)后有这么一段:
|
|
这下清楚了。。其实NSObject就是objc_object,一个OC对象其实就是objc_object或继承于objc_object的结构体的指针。。
objc_object结构体中也有一个isa_t类型的isa的成员,这个isa其实才是大家平时经常说的isa,之前在NSObject中的那个,其实是无效的,就是让大家看一下,如同runtime.h中的objc_class结构体定义一样,其实都已经失效了,真正的实现都被隐藏掉了,不过思路是正确的,依旧可以参考。
好,继续看isa_t是哪路神仙。
|
|
原来isa_t是个union,对union不了解的同学可以看这篇文章,大概意思可以看上面那段代码的注释。
其中cls就不用多解释了吧,bits不仅仅保存了指针,并对此做了优化,其中还包含了引用计数,析构状态,被其他 weak 变量引用情况,将来一一细表。
到此从整体上浏览了一下Class的结构,其中发现了一个很有意思的现象:
objc_object里有isa_t结构体类型的isa,这个isa包含当前objc_object的类的信息,其中有objc_class的指针,然后
objc_class又继承于objc_object,所以里面又有isa。。绕来绕去有点让人懵逼。别怕,让我们细细的捋一捋,结合后面那张图品味,味道更佳。
前面说过了,OC中的类其实就是一个指向objc_class结构体的指针(Class Object类对象),为了把OC中的类和对象区分开来,引入了一个叫做元类的概念(Meta Class),类对象的isa成员(也就是类对象的类)就是元类。它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似 [NSObject alloc]
的消息时,你事实上是把这个消息发给了一个类对象,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 [NSObject alloc]
这条消息发给类对象的时候,objc_msgSend()
会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。
关于Class的简单介绍就到此为止了,其实简单来说还是我上面说了好几次的,OC的实例对象就是一个指向objc_object的指针,objc_object中有一个isa_t的成员isa,isa包含当前对象指向的类的信息,OC中的类是一个objc_class的结构体,该结构体继承于objc_object,所以OC中的类也是一个对象,类对象中的isa_t
结构体包含了类对象指向的元类(meta-class,元类也是objc_class类型)的信息。
如同绕口令一般。
OC对象
我们先定义一个类。
|
|
还是通过 clang -rewrite-objc
(把oc代码转写成c/c++代码)转之后,代码如下。
|
|
通过以上代码可以看出FoobarClass类实例化的对象在内存中的是什么样子。
|
|
之前说过OC的对象是指向结构体实例的指针,所以有两个结论:
- 成员isa的地址就是对象的首地址的值。
- 其他成员变量的地址就是对象的首地址地址加上相应的偏移量。
请看下面例子。
|
|
在之前已经介绍过类对象和元类,以及他们的关系,在这里需要强调一下的是,一个OC对象的对象(-)方法,是在类对象里保存的;而类(+)方法,是在元类里保存的。
通过这个巧妙的设计,可以很方面的区分对象方法和类方法,使得OC中的对象和类达到了结构上的统一。方法的调用的逻辑看着也非常简约:
- 对象方法调用时,通过OC对象的isa在类中查找方法的实现。
- 类方法调用时,通过类对象的isa(OC对象的isa的isa)在元类中获取方法的实现。(OC对象的类方法,就是元类中的对象方法)
总结
为了有个整体的理解,开篇全讲类和对象了,很多东西都是浅尝辄止,后面会进行深入的探讨,一一补全。