iOS - 回顾Objective-C的对象模型
Objective-C对象模型
Objective-C
的Runtime
是开源的,可以在这里下载,在objc4-532.2
以后,苹果把NSObject
的实现也开源了
isa 指针
Objective-C
是一门面向对象的编程语言。每一个对象都是一个类的实例。在 Objective-C
语言的内部,每一个对象都有一个名为isa
的指针,指向该对象的类。
打开刚刚下载的源码,在Object.h
文件中可以看到Object
类,该类拥有isa
成员,类型为Class
@interface Object
{
Class isa; /* A pointer to the instance's class structure */
}
同样在objc.h
文件中找到Class
,并且可以看到 Objective-C
中的对象的定义struct objc_object
,是的,Objective-C
中的对象本质上是结构体对象,其中 isa
是它唯一的私有成员变量。 如下所示
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
由上可知,Class
为objc_class
类型的指针,并且还有看到我们熟悉的id
指针,因为id
是struct objc_object
类型的指针,所以id
类型可以指向任意对象。
下面看看objc_class
结构体,在objc-class.h
文件中可以找到定义
/*
* Class Template
*/
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;
#if defined(Release3CompatibilityBuild)
struct objc_method_list *methods;
#else
struct objc_method_list **methodLists;
#endif
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
可以看到objc_class
是类模版,即类的定义为struct objc_class
,所以 Objective-C
中类也是一个结构体对象。
由struct objc_class
类的定义,我们可以了解很多东西:
每一个类描述了一系列它的实例的特点,包括:成员变量的列表,成员函数的列表,缓存,协议等。每一个对象都可以接受消息,而对象能够接收的消息列表是保存在它所对应的类中。
元类
在Objective-C
语言中,每一个类实际上也是一个对象。每一个类也有一个名为 isa 的指针。每一个类也可以接受消息,例如:[NSObject alloc]
,就是向NSObject
这个类发送名为alloc
消息。
因为类也是一个对象,那它也必须是另一个类的实列,这个类就是元类 (metaclass)。元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,直到一直找到继承链的头。
元类 (metaclass) 也是一个对象,那么元类的 isa 指针又指向哪里呢?所有的元类的isa
指针都会指向一个根元类 (root metaclass)。根元类 (root metaclass) 本身的 isa 指针指向自己,这样就行成了一个闭环。
isa 和继承的关系如下图

可以从图中看出:
-
1、创建一个
OC类
其实是创建一个类对-类和元类,类的isa指针
指向元类,元类的isa
指向根元类,根元类的isa
指向它自己,形成闭环,这样保证isa
指针永远不会为空,同时这也会使得一个类可以响应NSObject
的所有实例方法 -
2、
isa指针
指向就是消息传递的方向,如果通过isa指针
没找到此消息,则继续通过superClass指针
查找,如下图所示。 -
3、类中含有实例方法列表,元类中含有类方法列表;
NSObject
元类的superClass
指向NSObject
实例,所以,理论上给元类发消息,能找到NSObject
的实例方法。
类的成员变量
如果把类的实例看成一个C语言的结构体(struct),上面说的isa 指针
就是这个结构体的第一个成员变量,而类的其它成员变量依次排列在结构体中
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;
#if defined(Release3CompatibilityBuild)
struct objc_method_list *methods;
#else
struct objc_method_list **methodLists;
#endif
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
从上面的Objective-C
对象模型中可以看到,除了isa
指针外还有很多其他成员,比如标记父类的super_class
, 表示类名的name
,version
为一个long
类型, 应该表示这个对象的版本号,instance_size
应该表示这个类对象大概在内存中占用的大小等。
- ivars
对象中的ivars
是指向成员属性列表的指针:
/*
* Instance Variable Template
*/
typedef struct objc_ivar *Ivar;
struct objc_ivar_list {
int ivar_count;
#ifdef __alpha__
int space;
#endif
struct objc_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __alpha__
int space;
#endif
} ivar_list[1]; /* variable length structure */
};
有数量标记ivar_count
、空间标记 space
以及ojbc_ivar
- method_list
对象中的methodList
是指向实例变量列表的指针
typedef struct objc_method *Method;
struct objc_method_list {
#if defined(Release3CompatibilityBuild)
struct objc_method_list *method_next;
#else
struct objc_method_list *obsolete;
#endif
int method_count;
#ifdef __alpha__
int space;
#endif
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
} method_list[1]; /* variable length structure */
};
obsolete
根据名字来猜,指向废弃的函数列表,method_count
是函数个数,space
为空间大小,然后objc_method
中有函数选择器method_name
, 描述类型(包含参数类型以及返回值类型)的字符串method_types
以及函数指针method_imp
。
- Cache
对象中的cache
对象类型为objc_cache
的结构体
typedef struct objc_cache * Cache;
struct objc_cache {
unsigned int mask; /* total = mask + 1 */
unsigned int occupied;
Method buckets[1];
};
Cache
为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在 isa
指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了,而是优先在 Cache
中查找。 Runtime
系统会把被调用的方法存到Cache
中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),这样下次查找的时候效率更高。
- Protocol
对象中的protocols
表示遵循的协议列表
@class Protocol;
struct objc_protocol_list {
struct objc_protocol_list *next;
int count;
Protocol *list[1];
};
指向下一个协议列表的指针next
,表示协议个数的count
以及协议列表 list[1]
,
可变与不可变
因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化。所以无法在运行时动态给对象增加成员变量。
相对的,对象的方法定义都保存在类的可变区域中。方法的定义列表是一个名为methodLists
的指针的指针(如下图所示)。通过修改该指针指向的指针的值,就可以实现动态地为某一个类增加成员方法。这也是Category
实现的原理。同时也说明了为什么Category
只可为对象增加成员方法,却不能增加成员变量。

注意,通过objc_setAssociatedObject
和 objc_getAssociatedObject
方法可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构。
除了对象的方法可以动态修改,因为isa
本身也只是一个指针,所以我们也可以在运行时动态地修改isa
指针的值,达到替换对象整个行为的目的。不过该应用场景较少。