Objective-C的底层实现(类与对象)
概述
对象
Objective-C中,类和对象的底层数据结构,可以参考Objective-C底层数据结构。
其中对象是通过struct objc_object
结构实现
typedef struct objc_object *id;
创建一个Son
类继承自Father
类,Father
类继承自Grandfather
类;这样的对象在运行时候,会产生如下图的结构形式
我们把这个转换成种更容易理解的数据组织形式
真正的内存形式是这样的
从对象内存形式上来看,对象内的变量成员,是从祖类继承而来(子成父业啊),在对象内部生成副本。从对象的内存组织来看,对象本身并不关心行为(对象的方法或实例方法),重点都在数据的组织上。
对象中的isa指针
对象的都有一个
isa
实例变量,它是从继承层次最高的NSObject
继承而来,isa
是表示对象的关键。在Objective-C
中,是不是第一等对象,isa
就是其标志,就好像汽车都有一个标识符号一样,看到这个标识就能想到是什么品牌的车了。
isa
是一个指针,指向了该对象的类。实质上,同一个类的实例,都指向同一个类对象(类也是一种特殊对象)。类中包含了实例方法,也就是说,同一个类的所有实例共用了这些实例方法。消息就是发送给对象,对象转交给其isa
指向类去处理,这种现象类似于当下火热的云计算。
Objective-C的这种设计,既可以友好地实现面向对象,又可以有效地节约内存。降低冗余数据,对象对方法的调用是通过isa
间接去调用,这样就造成了方法调用的动态性,主要原因是:
- 1、一个对象并不晓得它能否应答一个方法,它本身既不包含方法的实现,也不包含有方法的指针,而是间接通过isa转到自己的类才能知道。
- 2、类中的实例方法是以链表形式存在,运行时候,可以修改链表中的实例方法,即可以增删改查,这与C中的函数默认都是extern的不同。
从上面的分析,isa
的意义就更加重大,倘若没有isa
,一个对象就跟char
、int
类型等没啥区别了,不具备回应消息的本领。所以,isa
是什么,就好比是古时候官员的乌纱帽,有乌纱帽,就有权力,乌纱帽没了,就是凡夫俗子。
类
类是通过struct objc_class
结构实现的
typedef struct objc_class *Class;
在运行时,将会产生如下图这样的结构
在Objective-C
的世界里,一看到isa
的第一个反应就是,咦,对象!是的,Objective-C
中的类其实也是一种对象。
既然天地生的万物,那么天地又是何物?如果人类是上古神仙女娲所造,那女娲又因何而生呢?一般程序语言,对象的尽头都是自己生了自己。自己下个蛋,爬出来了自己,似乎是悖逆的,但确实如此。作为根类的NSObject
就是这样一个家伙!这看起来确实很困惑,但是譬如生物学中的造血干细胞可谓细胞的制造厂,但是造血干细胞又是谁制造的呢,咦,也是造血干细胞。
首先,类对象也是一种对象,那么它也会有自己的行为,这种行为称作类方法。与一般的类实例一样,类对象也不具备处理类方法的能力,也是要借助isa找到它所属的类,既元类,去调用类方法,类对象本身也是专注于数据的存储和布局形式;可参考类和元类。
但与常规的类实例不同,一般而言类对象的字段是固定的。即它默认情况下总是包含isa
、super_class
、name
、version
、info
、instance_size
、ivars
、methodLists
、cache
、protocols
。
一个类的数据形式会是如下
而它的数据在内存中的组织形式也是线性的
我们知道,在Objective-C
中,我们一般的定义形式是声明一个类的实例变量、属性、实例方法和类方法。并不能声明类变量,所以一般而言类对象的数据形式就是如上图的那些固定的字段。除了我们介绍的isa,剩余的字段的含义如下:
| 字段名 | 含义 |
| ----:---- | ----:---- |
| super_class | 指向父类的指针。因为Objective-C借鉴了SimTALK,在类的继承实现上,是通过一条继承链实现的。super_class就是整个继承链的核心字段 |
| name | 类的名字 |
| version | 版本 |
| info | 信息 |
| instance_size | 实例的内存大小 |
| ivars | 是一个指向实例变量列表的指针 |
| methodLists | 是一个指向实例方法列表的指针 |
| cache | 缓存了常用的实例方法 |
| protocols | 是一个指向协议列表的指针 |
我们可以这样简单地去解释运行时的Objective-C
的数据结构的含义
- 1.对象与实例变量有关,对象自身存储着实例变量
- 2.类对象与实例方法有关,实例方法必须通过类对象才能知晓
- 3.元类与类方法有关, 类方法必须通过元类才能知晓
运行时的类和对象
运行时的类和对象如下图
一般而言
- 1.isa关于对象是什么类
- 2.super_class关于继承链
- 3.所有元类都有同样的元类,因为他们的isa都指向同一个根元类
苹果已经将代码开源了,我们可以从:ObjC runtime 浏览源代码,或点此下载源代码。
从哪里入手呢?那当然是最基本的类与对象。与C++
相比,ObjC
中的类与对象结构要简洁与一致得多(参考《深度探索C++对象模型》,你就知道C++
中类与对象结构的复杂)。本文将详细讲解ObjC
中类与对象的结构,下回将讲如何在runtime
时操作类。
我们可以在/usr/include/objc/objc.h
和runtime.h
中找到对class
与object
的定义:
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
Class
是一个objc_class
结构类型的指针;而id
(任意对象)是一个objc_object
结构类型的指针,其第一个成员是一个objc_class
结构类型的指针。注意这里有一关键的引申解读:内存布局以一个objc_class
指针为开始的所有东东都可以当做一个object
来对待!那objc_class
又是怎样一个结构体呢?且看:
struct objc_class
{
struct objc_class* isa;
struct objc_class* super_class;
const char* name;
long version;
long info;
long instance_size;
struct objc_ivar_list* ivars;
struct objc_method_list** methodLists;
struct objc_cache* cache;
struct objc_protocol_list* protocols;
};
objc_class
结构体的各成员介绍如下:
isa
:是一个objc_class
类型的指针,看到这里,想起我前面的引申解读了没?内存布局以一个objc_class
指针为开始的所有东东都可以当做一个object
来对待!这 就是说objc_class
或者说类其实也可以当做一个objc_object
对象来对待!对象是对象,类也是对象,是不是有点混淆?别急,ObjC
发明(or重用)了一个术语来区分这两种不同的对象:类对象(class object
)与实例对象(instance object
)。OK,名称混淆的问题解决,下面我将使用这两个术语来区分不同的对象,而使用“对象”这一术语来泛指所有的对象。ObjC
还对类对象与实例对象中的isa
所指向的类结构作了不同的命名:类对象中的isa
指向类结构被称作metaclass
,metaclass
存储类的static
类成员变量与static
类成员方法(+开头的方法);实例对象中的isa
指向类结构称作class
(普通的),class
结构存储类的普通成员变量与普通成员方法(-开头的方法)。
super_class
:一看就明白,指向该类的父类呗!如果该类已经是最顶层的根类(如NSObject
或NSProxy
),那么super_class
就为NULL
。
好,先中断一下其他类结构成员的介绍,让我们理清一下在继承层次中,子类、父类、根类(这些都是普通 class
)以及其对应的metaclass
的isa
与super_class
之间关系:
规则一:类的实例对象的isa
指向该类;该类的isa
指向该类的metaclass
;
规则二:类的super_class
指向其父类,如果该类为根类则值为NULL
;
规则三:metaclass
的isa
指向根metaclass
,如果该metaclass
是根metaclass
则指向自身;
规则四:metaclass
的super_class
指向父metaclass
,如果该metaclass
是根metaclass
则指向该metaclass
对应的类;
好吧,文字总是那么乏力,有图有真相!
那么class
与metaclass
有什么区别呢?class
是instance object
的类类型。当我们向实例对象发送消息(实例方法)时,我们在该实例对象的class
结构的methodlists
中去查找响应的函数,如果没找到匹配的响应函数则在该class
的父类中的methodlists
去查找(查找链为上图的中间那一排)。如下面的代码中,向string
实例对象发送lowercaseString
消息,会在NSString
类结构的methodlists
中去查找lowercaseString
的响应函数。
NSString * string;
[string lowercaseString];
metaclass
是class object
的类类型。当我们向类对象发送消息(类方法)时,我们在该类对象的metaclass
结构的methodlists
中去查找响应的函数,如果没有找到匹配的响应函数则在该metaclass
的父类中的methodlists
去查找(查找链为上图的最右边那一排)。如下面的代码中,向NSString
类对象发送stringWithString
消息,会在NSString
的metaclass
类结构的methodlists
中去查找stringWithString
的响应函数。
[NSString stringWithString:@"string"];
好,至此我们明白了类的结构层次,让我们接着看类结构中的其他成员。
name
:一个C
字符串,指示类的名称。我们可以在运行期,通过这个名称查找到该类(通过:id objc_getClass(const char *aClassName))
或该类的metaclass(id objc_getMetaClass(const char *aClassName))
;
version
:类的版本信息,默认初始化为0。我们可以在运行期对其进行修改(class_setVersion
)或获取(class_getVersion
)。
info
:供运行期使用的一些位标识。有如下一些位掩码:
CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含实例方法和变量;
CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
CLS_INITIALIZED (0x4L) 表示该类已经被运行期初始化了,这个标识位只被 objc_addClass 所设置;
CLS_POSING (0x8L) 表示该类被 pose 成其他的类;(poseclass 在ObjC 2.0中被废弃了);
CLS_MAPPED (0x10L) 为ObjC运行期所使用
CLS_FLUSH_CACHE (0x20L) 为ObjC运行期所使用
CLS_GROW_CACHE (0x40L) 为ObjC运行期所使用
CLS_NEED_BIND (0x80L) 为ObjC运行期所使用
CLS_METHOD_ARRAY (0x100L) 该标志位指示 methodlists 是指向一个 objc_method_list 还是一个包含objc_method_list 指针的数组;
instance_size
:该类的实例变量大小(包括从父类继承下来的实例变量);
ivars
:指向objc_ivar_list
的指针,存储每个实例变量的内存地址,如果该类没有任何实例变量则为NULL
;
methodLists
:与info
的一些标志位有关,CLS_METHOD_ARRAY
标识位决定其指向的东西(是指向单个objc_method_list
还是一个objc_method_list
指针数组),如果info
设置了CLS_CLASS
则objc_method_list
存储实例方法,如果设置的是CLS_META
则存储类方法;
cache
:指向objc_cache
的指针,用来缓存最近使用的方法,以提高效率;
protocols
:指向objc_protocol_list
的指针,存储该类声明要遵守的正式协议。
总结:
ObjC
为每个类的定义生成两个objc_class
,一个即普通的class
,另一个即metaclass
。我们可以在运行期创建这两个objc_class
数据结构,然后使用objc_addClass
动态地创建新的类定义。这个够动态够强大的吧?下回讲演示如何在运行期动态创建新类。
参考
- Objective-C底层数据结构
- 类和元类