写在前面
稍微了解OC运行时机制的人应该都知道:
1. 类和对象都是id,在给你一个id的前提下无法直观的知道这个对象是类对象还是类本身。简单的可以简化成runtime管理的都是id (id的本质其实是objc_object, objc_class头部其实就是id, 也就是isa)。
2. Class在objc中是动态创建的,selector, method, imp,protocol等都是随后绑定上去的(即所谓的运行时绑定)。
3. 通过runtime能够查出当前运行时环境中所有的类,每个类中的方法,每个类消息的绑定,每个类的实现的协议,每个协议的定义,每个类当前的消息缓存等一切你想知道的东西。
4. 类的方法(消息)调用是间接的。
正文
好的,在我们知道上述知识以后。我们来研究下,在不存在NSObject的情况下,怎样动态创建一个类。
图中几个方法的用法和作用不再赘述,不清楚的可以自行查阅官方文档。有一点需要注意,我们全程都不能使用 ARC,因为 ARC 模式下从void *转换到id是需要有一个 bridge 的过程,而这个过程仍然依赖于NSObject完成,所以我们又会陷入一个需要NSObject的死循环。
运行起来会报以下错误:
查看文档得知,每当一个无效Selector被发出并没有得到响应,运行时系统将调用doesNotRecognizeSelector:,该方法执行后会引发NSInvalidArgumentException,并生成错误消息。那好吧,check一下。
再次运行。。。
我们似乎忘了实现initialize方法。。。该方法会在某个类第一次被初始化时调用,加上它。
再run一次
额,alloc类方法是如何实现的?这个时候,我们就需要下载Runtime源码一探究竟了。。。
我们知道,在OC中,任何类都是继承自NSObject这一基类。这么一说,alloc类方法应该也是NSobject实现的。
我们打开源码,,找到NSObject.mm这个文件,并通过导航菜单快读定位到alloc方法的位置
按住 ⌘ 一路点击跟踪,直到这里:
我们发现,在Objc2.0之后新增了一种自定义的快捷构造方法。但是殊途同归,事实上它们最终都要调用class_createInstance这个方法,我们来看看:
在老的源码中,这里会判断是否使用GC。iOS上是不能使用GC的,这是为Mac设计的,既然新版本去掉这个判断,我们也就没有必要去理会了。继续跟踪,我们来到了_class_createInstanceFromZone函数:
在这里,runtime主要做了有关内存对齐的一些计算,由于zone是nil,这里直接用calloc申请了一块内存。calloc 与 malloc 的区别,可以自行谷歌。
好的,接下来就剩最后一步了,objc_constructInstance函数:
这里回归了我们一开始说的,对象的isa指针。我们也说了,id的本质其实是objc_object,那么objc_object到底是什么:
objc_object其实就是一个C++结构体,有权限控制,有成员函数。通过我们刚才提到的objc_object::initIsa函数,可以知道,就是把对象的isa指针指向这个类的元信息Class。
看到这里,我们其实就已经可以为我们的类写一个alloc方法了。但是似乎有个问题无法解决,objc_object对于开发者而言并不能接触到,我们有必要通过直接修改内存的方式去修改其isa变量。那么,回到源码,我们看看isa那个isa_t类型究竟是什么:
可以发现,它是个联合体。从源码中我们也可以看到,它在初始化的时候直接被当做uintptr_t对待,而uintptr_t又是unsigned long的typedef,所以我们可以写出如下代码:
好了,把实现的alloc方法添加到我们的类中,然后用初始化一个实例对象,再执行。
到此为止,在不存在NSObject的情况下,动态创建一个类的工作完成了。