要点:
1.什么是Runtime
2. iOS RunTime解析
3. Method-swizzling
什么是Runtime?
Runtime,即运行时,通常我们说的Runtime是指程序的后台的运行环境。
传统的面向过程的语言开发,例如c语言,编译器会直接把代码变成最底层的机器指令,变量、函数都变成地址偏移。程序运行时CPU只要一条条的处理就行了。
这种机制比较死板,也缺乏跨平台特性,因此现在的大多数编程语言已经不再直接生成机器代码,而且是使用一套托管环境。编译器会把代码编译成一种类似于脚本的中间代码,在运行时再由托管程序来执行。
Objective-C诡异的地方在于这一套中间语言API是透明的,我们可以通过调用这套API来做一些超乎想相的事情。
iOS RunTime解析
1. 了解NSObject
要了解iOS的runTime首先就要了解下NSObject,它是OC所有类的基类,先看下他的定义,重点如下,
@interface NSObject <NSObject> {
Class isa;
}
可以看到NSObject核心其实就是定义了一个叫isa的Class变量,那么这个Class是什么呢?再往下看Class的定义,
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Classsuper_class ; // 指向父类
constchar *name ; // 类名
long version; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
long info; //一些标识信息,如CLS_CLASS(0x1L) 表示该类为普通 class ,其中包含实例方法和变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
longinstance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
structobjc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols; // 存储该类声明遵守的协议
#endif
} OBJC2_UNAVAILABLE;
很明显NSObject的核心其实是一个叫objc_class的数据结构,其中保存着关键的数据,其中super_class是其父类的指针,name是类名,ivars是这个类的变量列表,methodLists是函数列表,cache是方法的缓存,protocols是一个protocol的list。
所以OC代码执行的时候,首先会去找函数cache,里头有没有对应函数,如果找不到,会去找methodLists列表中是否含有对应的函数,如果再没有就会去找super_class 中的cache,然后.....
实际上我们在进行函数调用时runtime会调用一个叫objc_msgSend的函数,这个函数封装了对类中方法的查找操作。
具体的工作过程类似于这样,
首先我们需要一个Selector,它的定义如下,
// Anopaque type that represents a method selector.
typedefstruct objc_selector *SEL;
其实就是一个变量或函数的唯一标示符(id),所有的查找都是通过它来进行的。
下面是苹果Objective-C RuntimeReference文档中对它们的描述:
选择器(typedefstruct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射。
有了sel我们就可以找到一个IMP
IMP method = objc_msg_lookup (class->isa, sel);
IMP的定义如下,
typedef id (*IMP)(id, SEL, ...)
这种数据类型其实就是实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约。第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向元类(metaclass)的指针)。第二个参数是方法的选择器,后面跟的都是参数。
最后调用它,完毕。
method(person, sel, @"p1", @"p2");
2.Objective-C Runtime Reference
Objective-C提供了一整套的API用来做我刚刚提到那些事情,通过反射查找函数、变量,修改函数变量列表等等,你几乎可以改变一切,下面是几个典型的函数。
发消息:objc_msgSend
带返回值的消息:voidobjc_msgSend_stret ( id self, SEL op, ... );
发消息给父类:objc_msgSendSuper
增加函数:class_addMethod
增加实例变量:class_addIvar
增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成
增加Protocol:class_addProtocol
获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod
获取属性列表及每个属性的信息:class_copyPropertyList
获取类本身的信息,如类名等:class_getName
获取变量列表及变量信息:class_copyIvarList
将实例替换成另一个类:object_setClass
将函数替换成一个函数实现:class_replaceMethod
详见Apple的文档:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html
Method-swizzling
在计算机学科中,指针变换(pointer swizzling)是指将基于名字或位置的引用转变为直接的指针引用,Method Swizzling与之类似,指的是改变一个已存在的选择器对应的实现的过程。
下面是一个典型的例子,
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
From <http://www.cocoachina.com/industry/20140225/7880.html>