Objective-c runtime (一)
一 运行时系统相关特性
- 使用
Objective-C
中的消息传递特性可以调用类和对象中的方法。对象消息传递是一种动态特性,既接收器和接收器中被调用的方法是在运行时确定的。 -
消息传递表达式包含接收器(接受消息的对象/类)和消息,而消息又由选择器和相应的输入参数构成。如图。
- 选择器是一种分段的文本字符串,每个分段以冒号结尾并且后跟参数。如上图。
- 选择器数据类型(SEL)是一种特殊的
Objective-C
数据类型,它用于在编译源代码时使用具有唯一性的标志符替换选择器。使用@selector
关键字或Foundation
框架中的NSSelectorFromString()
函数,可以创建类型为SEL
的变量。 - 使用动态类型功能可以在运行时决定对象的类型,因而能够由运行时因素决定在程序中使用哪种类型的对象,
Objective-C
通过id
数据类型支持动态类型。 - 使用动态方法决议能够以动态方式实现方法。使用
Objective-C
的@dynamic
指令可以告知编译器与某个属性关联的方法会以动态方式实现。NSObject
中含有resolveInstanceMethod:
和resolveClassMethod:
方法,使用他们能够以动态方式分别实现由选择器指定的实例和类方法。 -
Foundation
框架中NSObject
类的API含有非常多用于执行对象内省的方法。在运行程序时,这些API能够以动态方式查询方法的信息。它们还可以测试方法的继承性、行为和一致性。
二 运行时系统的结构
运行时系统的组成部分
Objective-C
的运行时系统由两个主要部分构成:编译器和运行时系统库。
编译器
Objective-C
语言中的面向对象元素和动态特性都是通过运行时系统实现的。概括来讲,运行时系统由下列部分组成:
- 类元素(接口、实现代码、协议、分类、方法、属性、实例变量)
- 类实例(对象)
- 对象消息传递(包括动态类型和动态绑定)
- 动态方法决议
- 动态加载
- 对象内省
当编译器解析使用了这些语言元素和特性的Objective-C源代码时,它会使用适当的运行时系统库数据结构和实现该语言特定行为的函数,生成可执行代码。例如
-
生成对象消息传递代码
当编译解析对象消息时,如:
[obj show]
它会生成调用运行时系统库中函数objc_msgSend()
的代码。该函数将接收器、选择器和消息传递的参数作为输入参数。因此编译器会将源代码中的所有消息传递表达式转换为调用运行时系统库函数objc_msgSend()
的代码。
源文件1.m内容如下:
#import@interface MyClass:NSObject - (void)show; @end @implementation MyClass - (void)show{ NSLog(@"%@", @"show"); } @end int main(int argc, char *argv[]) { @autoreleasepool { MyClass *obj = [[MyClass alloc] init]; [obj show]; } }
经过
clang -rewrite-objc 1.m
会得到编译后的cpp文件,相关内容如下:
-
**生成类和对象的代码
当编译器解析含有类定义和对象的源代码时,它会生成相应的运行时数据结构。
Objective-C
中的类与运行时系统中的Class数据结构对应。Class
是指向objc_class
不透明数据类型的指针。
typedef struct objc_class Class
Objective-C
对形象与运行时系统中的objc_object
数据结构对应。
struct objc_object
{
Class isa;
//含有实例变量的长度可变数据
}
实际上所有的Objective-C对象的类和对象对应的运行时数据结构(objc_object
和objc_class
)都是以isa
指针开头(对应的运行时数据结构的包含的第一个元素是isa
)。
下面的实例程序可以验证。
#import
#import "LZPerson.h"
#import
#import
@interface LZDog: NSObject{
NSInteger _age;
}@end @implementation LZDog - (instancetype)initWithAge:(NSInteger) age{ self = [super init]; if (self) { _age = age; } return self; } @end int main(int argc, const char * argv[]) { @autoreleasepool { LZDog *dog1 = [[LZDog alloc] initWithAge:0x123456780a0b0c0d]; LZDog *dog2 = [[LZDog alloc] initWithAge:0xaabbccdd12345678]; //get the objec size NSInteger objcSize = class_getInstanceSize([LZDog class]); //打印对象的内存数据 NSData *dog1Data = [NSData dataWithBytes:(__bridge void*)dog1 length:objcSize]; NSData *dog2Data = [NSData dataWithBytes:(__bridge void*)dog2 length:objcSize]; //查看对象内存 NSLog(@"dog1: %@", dog1Data); NSLog(@"dog2: %@", dog2Data); //打印类地址 NSLog(@"class address:%p", [LZDog class]); //查看类内存 id dogClass = objc_getClass("LZDog"); NSInteger classSize = class_getInstanceSize([dogClass class]); NSData *classData = [NSData dataWithBytes:(__bridge void*)dogClass length:classSize]; NSLog(@"dogclass: %@", classData); //打印父类(NSobject)地址 NSLog(@"superclass address: %p", [LZDog superclass]); } return 0; }
运行结果如下:
2015-10-04 11:25:01.297 testRuntime01[1186:87309] dog1: <78240000 01000000 0d0c0b0a 78563412>
2015-10-04 11:25:01.298 testRuntime01[1186:87309] dog2: <78240000 01000000 78563412 ddccbbaa>
2015-10-04 11:25:01.298 testRuntime01[1186:87309] class address:0x100002478
2015-10-04 11:25:01.298 testRuntime01[1186:87309] dogclass:
2015-10-04 11:25:01.298 testRuntime01[1186:87309] superclass address: 0x7fff780c30f0
分析结果如图:
运行时系统库
苹果公司提供的Objective-C
运行时系统库实现了Objective-C
的面向对象特性和动态属性。我们可以在代码中直接运用运行时系统提供的API。下面代码实例展示了如何在代码中使用运行时系统API。
import
import "LZPerson.h"
import
import
/*
1. 动态创建一个类 LZStudent(继承自NSObject)
2. 动态添加实例变量 name
3. 动态添加成员方法 show
*/
static void show(id self, SEL _cmd){
NSLog(@"show");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建一个类,继承自NSobject
Class LZStudent = objc_allocateClassPair([NSObject class], "LZStudent", 0);
BOOL isOk;
//向该类添加一个实例变量 NSString *_name
isOk = class_addIvar(LZStudent, "_name", sizeof(NSString *), log2(sizeof(id)), @encode(NSString));
if (!isOk) {
NSLog(@"addivar fail");
}
//想该类添加一个实例方法。
isOk = class_addMethod(LZStudent, NSSelectorFromString(@"show"), (IMP)show, "v@:");
if (!isOk) {
NSLog(@"addMethod fail");
}
objc_registerClassPair(LZStudent);
unsigned int count ;
Ivar *arr = class_copyIvarList(LZStudent, &count);
//打印类变量的个数
NSLog(@"variable count: %u", count);
//打印变量的名字和类型
for (NSInteger i = 0; i < count; i ++) {
const char* ivarName = ivar_getName(arr[i]);
const char* ivarType = ivar_getTypeEncoding(arr[i]);
NSLog(@"name: %s, type: %s", ivarName, ivarType);
}
id obj = [[LZStudent alloc] init];
[obj performSelector:NSSelectorFromString(@"show")];
}
return 0;
}
运行结果如下
2015-10-04 15:25:19.057 testRuntime01[1537:152205] variable count: 1
2015-10-04 15:25:19.058 testRuntime01[1537:152205] name: _name, type: {NSString=#}
2015-10-04 15:25:19.058 testRuntime01[1537:152205] show
运行时系统库数据类型和函数为运行时系统库提供了实现各种Objective-C
特性(如消息传递)所必须的数据类型核函数。如图
当程序想对象发送消息时,运行时系统会通过自定义代码中的类方法缓存和虚函数表,查找类的实例方法。为了找到相应的方法运行时系统会搜索整个类的层次结构,找到方法后,它就会执行方法的实现代码。运行时系统库含有非常多用于实现消息传递的设计机制。下面分别介绍这几个机制:
- 通过虚函数表查找方法
运行时系统定义了一种方法数据类型(objc_method
)代码如下
struct objc_method
{
SEL method_name;
char *method_type;
IMP method_imp;
}
typedef struct objc_method Method;
因为在执行程序的过程中调用方法的操作可能执行数百万次,所以运行时系统使用了一种快速高效的方法查询和调用机制。虚函数表是运行时系统使用的动态绑定的支持机制。Objective-C
运行时系统实现了一种自定义的虚函数分派机制,专门用于最大限度的提高性能和灵活性。
虚函数表是一个用于存储IMP
(Objective-C方法的实现代码)的数据数组。每个运行时系统类实例(obcj_class
)都有一个纸向虚函数表的指针。
每个尅实例还拥有最近调用过方法的指针缓存。运行时系统库先搜索缓存,如果没找到,再去虚函数表去查找,如果在虚函数表找到了就将IMP存储到缓存中备用。 - 消息分派
当我们向对象发送消息时比如[obj message ]
,编译器会根据对象找到对应的类,然后从类(及其父类)的缓存和虚函数表(参考1)中查找,并将其转换为一条标准的C语言函数调用objc_msgSend()
()。如果没有找到符合的方法,那就执行消息转发。
消息转发分为两大阶段。第一阶段先征询接受者所属的类,看其能否动态添加方法,这叫做动态方法决议(resolveInstanceMethod:)
。如果我们没有动态的添加方法实现那么热就会进入第二阶段。首先运行时系统请接受着看看能否将消息转发给其他的对象,如能则该消息有其他对象处理。这成为快速转发(forwardingTargetForSelector:)
。如果没有合适的对象处理消息,运行时系统会把与消息有关的全部细节都封装到NSInvocation
中,再给接受者最后一次机会。整个过程如图所示。
示例代码 - 访问类实例方法
当我们向类发送消息时,如[NSobject alloc]
,运行时系统是通过元类实现的。
元类
是一种特殊的类对象,运行时系统使用其中含有的信息能够找到并调用类方法。每个类都有一个独一无二的元类,元类中存放着类的类方法列表(类的实例方法存放在类里,类方法列表存放在元类那里)。
Objective-C
的对象的isa
指针指向类,类的isa
指向元类。
综上所述运行时系统通过下列方式对实例和类方法执行消息传递操作。- 当向对象发送消息时,会通过类的缓存和虚函数表,获得相应的方法。
- 当向类发送消息时, 会通过元类的缓存和虚函数表,获得相应的方法。