我们都知道,Objective-C是一门动态语言,它的Runtime机制往往能让我们实现一些黑魔法。那么,这门语言是如何实现它的动态特征的呢?
下面我们来刨根问底看个究竟。
在Objective-C中,我们用到的几乎所有的类都是NSObject类的子类,NSObject类的定义如下:
@interface NSObject <NSObject> {
Class isa;
}
就一个Class 类型的isa实例变量,Class是什么类型?我们切入看一下
Class 是一个 struct objc_class *的指针,struct objc_class定义如下:
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;
看到这里大家可能又晕了,怎么又有一个isa?这些isa是什么?之间有什么联系?
其实,Objective-C中任何类定义都是一个对象。即在程序启动的时候任何类定义都对应于一块内存。在编译的时候,编译器会给每一个类生成一个且只生成一个”描述其定义的对象”,也就是苹果公司说的类对象(class object),他是一个单例(singleton)。每个实例的isa指针就会指向它对应的类对象。而这个类对象会存储它的实例的相关信息,包括实例方法、实例变量、实现的协议等。
从上面的结构体中我们可以看到,类对象也拥有一个isa指针,这个指针又指向何处呢?
我们知道,对象都是由类实例化而来的,那么类对象也是某个类实例化而来的,类对象的isa指针就指向这个实例化他的类的对象,我们称这个类为元类,元类存储了类的相关信息(其中主要包括类方法列表)。
还是有些不明白?下面这个图标可能会有些帮助:
也就是说,在编译的时候,编译器会对每一个类生成一个类对象和一个元类对象,来存储这个类的相关信息。当我们实例化一个对象的时候,是根据这个类对象包含的信息实例化的。
既然这类是一个对象,那么,它的成员值就可以改变,这就个了我们动态改变一个类的无线可能。
我们可以动态的向方法列表中添加方法、甚至新添加一个类等等。
但是我们不能动态的添加实例变量,因为实例变量存储在ivars中,而ivars是struct objc_ivar_list *类型,在编译完成后类对象的存储空间不能改变,也就意味着ivars不能改变,因而不能动态的添加实例变量。这也是分类中不能添加实例变量的原因。
可能有的童鞋会问,那为什么可以添加实例方法呢?
从类的结构体中可以发现,methodLists是struct objc_method_list **类型的,意味着类对象中只是存了一个列表的指针,而实际方法列表的容器并不存储在类对象中。