XiaoboTalk

Objective-C 对象模型

先来看一段代码:
@interface NSObject (Test) - (void)test; // 注意这里是实例方法 @end @implementation NSObject (Test) - (void)test { NSLog(@"Hello, test! in NSObject - test method"); } @end /** SubClass **/ @interface SubClass: NSObject @end @implementation SubClass @end int main(int argc, const char * argv[]) { [SubClass test]; // 这里调用的是类方法 +test return 0; }
我认为这是理解 Objective-C 对象模型的最佳案例。 上边的代码,不但能编译,还能正常的执行,并输出Hello, test! in NSObject - test method
SubClass继承了NSObject,但没有实现任何自己的方法。此外通过NSObject的 Category,为其增加了-test方法,注意这里是-实例方法。最终在main函数中调用[SubClass test],注意这里调用的是SubClass+test方法,然而整个继承体系中都没有+test方法。但最终,调用了NSObject中的实例方法-test

isa 指针

Objective-C中一切都是对象,包括类本身也是个对象,叫类对象,它是元类的一个实例。而元类也是别人的一个实例,这个别人就是根元类。根元类也是别人的一个实例,然而这里的别人就是它自己了。而把这一切都联系起来的是一个指针isa,也就是is a,比如香蕉 is a 水果
notion image
注意,上图中的BooFoo对象没有任何继承关系,这里只是为了说明,所有对象的元类对象的isa指针都指向了根元类。
类对象 存储着类的 * 实例方法列表、 * 实例变量列表、 * 属性列表等信息;
元类对象 存储着
* 类方法列表、 * 类变量列表等信息。
每个类的类对象和元类对象在内存中都只有一份,这是由 Objective-C 运行时系统所保证的。当程序加载类时,类对象和元类对象都会被创建并存储在内存中,并且它们的地址是不变的。具体是由编译器和加载器共同完成,编译时,把类和元类的相关信息,直接写入二进制可执行文件对应的段(例如 objc_data section)。程序启动时,会被加载器(loader)把这些信息加载进内存。

superclass 指针

这要和isa指针区分开,superclass描述的是继承关系。只有类才有继承的概念,实例并没有,所以类对象和元类对象又都构成了自己的继承链。并且它们之间有微妙的联系。
notion image
Objective-C 在设计上认为 NObject类应当是一切其他类的超类(父类),就连根元类也不例外。唯独它自己则没有指向任何父类,属于天生地长。而方法在类的继承体系中是跟着superclass联调来查询的。所以现在,可以解释文章开头的奇怪现象了。
notion image
就消息本身而言,它不管-方法,还是+方法;不存在这个概念,因为不管类还是元类,它们本身也都是对象。只要方法签名一致,就可以调用。所以最终调用了NSObject-test()
接下来,我们把isasuperclass指针图画在一起,就构成了完成的对象模型图。
notion image

下一阶段

如果在继承体系中没有找到相应的方法,就会开启消息转发。这些其实都封装在 objc_msgSend 函数内部:
具体来说,objc_msgSend函数的工作可以分为以下几个步骤:
  1. 根据接收者对象的类(Class)和消息选择器(Selector),找到接收者对象的方法列表。
  1. 在方法列表中遍历,逐个比较方法的选择器与消息选择器是否匹配。
  1. 如果找到匹配的方法,则执行该方法的实现(IMP)。
  1. 如果在方法列表中找不到匹配的方法,则会进行消息转发处理。
 
下一篇,我们将讨论
Objective-C 的消息机制
(全文完!)