先来看一段代码:
@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 水果
。注意,上图中的
Boo
和Foo
对象没有任何继承关系,这里只是为了说明,所有对象的元类对象的isa
指针都指向了根元类。类对象 存储着类的 * 实例方法列表、 * 实例变量列表、 * 属性列表等信息;
元类对象 存储着
* 类方法列表、 * 类变量列表等信息。
每个类的类对象和元类对象在内存中都只有一份,这是由 Objective-C 运行时系统所保证的。当程序加载类时,类对象和元类对象都会被创建并存储在内存中,并且它们的地址是不变的。具体是由编译器和加载器共同完成,编译时,把类和元类的相关信息,直接写入二进制可执行文件对应的段(例如 objc_data section)。程序启动时,会被加载器(loader)把这些信息加载进内存。
superclass 指针
这要和
isa
指针区分开,superclass
描述的是继承关系。只有类才有继承的概念,实例并没有,所以类对象和元类对象又都构成了自己的继承链。并且它们之间有微妙的联系。Objective-C 在设计上认为
NObject
类应当是一切其他类的超类(父类),就连根元类也不例外。唯独它自己则没有指向任何父类,属于天生地长。而方法在类的继承体系中是跟着superclass
联调来查询的。所以现在,可以解释文章开头的奇怪现象了。就消息本身而言,它不管
-
方法,还是+
方法;不存在这个概念,因为不管类还是元类,它们本身也都是对象。只要方法签名一致,就可以调用。所以最终调用了NSObject
的-test()
。接下来,我们把
isa
和superclass
指针图画在一起,就构成了完成的对象模型图。下一阶段
如果在继承体系中没有找到相应的方法,就会开启消息转发。这些其实都封装在 objc_msgSend 函数内部:
具体来说,
objc_msgSend
函数的工作可以分为以下几个步骤:- 根据接收者对象的类(Class)和消息选择器(Selector),找到接收者对象的方法列表。
- 在方法列表中遍历,逐个比较方法的选择器与消息选择器是否匹配。
- 如果找到匹配的方法,则执行该方法的实现(IMP)。
- 如果在方法列表中找不到匹配的方法,则会进行消息转发处理。
下一篇,我们将讨论
Objective-C 的消息机制(全文完!)