Objective-C Runtime:类和对象

Objective-C

Objective-C是一门动态语言。其实Objecitve-C扩展了C语言,加入了面向对象特性和消息传递机制。简单的说,这个扩展的核心是Runtime库,它是Objective-C面向对象的和动态机制的基石。Objective-C是动态的语言,意味着它不仅需要一个编译器,还需要一个运行时系统动态的创建类和对象、进行消息传递和转发。充分理解Runtime机制,可以帮助我们更好的学习Objective-C。下面一起学习Runtime:

类(Class)

在Objective-C中,类和对象都是结构体。从头文件objc/objc.h中,可以查看定义。

1
2
typedef struct objc_class *Class;

查看objc/runtime.h中objc_class结构体的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

其中
1.isa:指向metaClass(元类)的指针。
2.super_class:执行该类的父类,如果该类是根类(NSObject或NSProxy),则super_class为NULL。
3.version:记录类的版本信息。主要用于对象的序列化,可以通过它识别出不同定义版本中实例变量布局的改变。
4.cache:用于缓存常用的方法。当接收对象收到消息时,根据isa指针去查找相应的对象。实际上,这个对象中有很多方法,只有一少部分经常使用,很多方法不能使用或者根本用不上。这种情况下,如果每次接收到消息都去遍历methodLists,性能较差,所以使用cache保存经常使用的方法,在接收到消息后,首先查找cache,如果没有则查找methodLists,提高查找效率。

对象

在objc/objc.h中,对象的定义

1
2
3
4
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;

objc_object是一个类的实例的结构体,其中只有一个指向其类的isa指针。当对象发送消息是,运行时库可以根据isa指针找到实例对象所属的类。运行时库会在类的methodLists和父类的methodLists中查找能响应的方法,找到后运行方法。

另外,id是objc_object类型的指针。通过它可以实现类似C++的泛型。

元类(Meta Class)

所有类自身也是一个对象,所以我们可以向类发送消息(调用类方法)。例如,

1
NSArray *array = [NSArray array];

+array消息发送给NSArray类,因为NSArray也是对象,所以它也是objc_object结构体,包含isa指针指向其类。那么,isa指针指向哪里呢?为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的objc_object结构体。所以就引出了meta-class是一个类对象的类
当向一个对象发送消息时,会在这个类的方法列表中查找方法;
当向一个类发送消息时,会在这个类的meta-class的方法列表中查找。meta-class 中存放着一个类所有的类方法。

这时可能又发现了一个问题,既然一切皆是类,那么meta-class的isa指针指向哪里呢?在Objective-C设计者为了防止无限循环,所有的meta-class的isa指向基类的meta-class,基类的meta-class指向自身。

1413628797629491.png

上图,虚线是isa的指向,可以看出每个isa都不为空,所以无论什么类型的对象,我们都可以通过isa获取对象信息。实线是superclass指向,通过该它可以方便查找父类,如果是根类在superclass为空。

常用操作

1
2
3
4
5
6
7
8
9
10
11
12
//类名
const char * class_getName(Class cls);

//父类
Class class_getSuperclass(Class cls);

//判断是否为元类
BOOL class_isMetaClass(Class cls);

//实例大小
size_t class_getInstanceSize(Class cls);

** 成员变量(ivars)及属性 **

在objc_class中,所有的成员变量、属性的信息是放在链表ivars中。ivars是数组,存放着指向每个元素的Ivar指针。以下方法是操作这个字段的。

成员变量操作方法

1
2
3
4
5
6
7
8
9
10
11
12
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );

// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

注意:class_copyIvarList需要手动释放free();

属性操作方法

1
2
3
4
5
6
7
8
9
10
11
12
// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );

// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );

// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

方法(methodLists)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );

// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

  • class_addMethod方法可以覆盖父类方法的实现,但是不会取代本类已存在的实现,如果存在同名的方法,则会返回NO。如果要修改本类中方法的实现,可以使用method_setImplementation。

  • class_getInstanceMethod,class_getClassMethod方法和class_copyMethodList不同,前者会搜索父类。

  • class_replaceMethod如果类中存在同名的,则会替换;如果不存在,会添加。

  • class_getMethodImplementation方法,该函数再向类实例发送消息是会调用,并返回一个指向方法实现的函数指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针是一个指向runtime内部的函数,而不一定是方法的实际实现。

  • class_respondsToSelector函数,通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来实现相同目的。

如果想看具体内容点这里

小结

本文介绍了Runtime运行时中类和对象相关的数据结构,通过数据结构可以更好的理解底层面向对象的实现。另外,列举了一些常用的操作方法。

参考资料:

1.Objective-C 中的类和对象 - Garan no Dou

2.What is a meta-class in Objective-C?

3.Objective-C Runtime - Apple

4.Objective-C Runtime 运行时之一:类与对象