先上官方地址:Objective-C Runtime Programming Guide
Objective-C是一门动态语言,它将静态语言在编译和链接时期做的事放在运行时处理Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译代码,这个运行时系统即Objc Runtime,运行时系统作为OC语言的操作系统。尤其是它在运行时动态的加载类,并且向其他对象转发消息。它同时提供在你的程序运行时如何找到对象的信息。
Objc Runtime是一个用C语言和汇编语言编写的库
Objc Runtime有两个版本:Legacy 和Modern 两个版本
在Legacy版本中,如果你在类中改变了实例变量的布局,你必须重新编译继承它的类。
在Modern版本中,如果你在类中改变了实例变量的布局,你不需要重新编译继承它的类。
iPhone应用和在OS X v10.5上运行的64位程序以及以后的版本使用Modern版本。
其他程序(OS X 32位程序)使用Legacy版本。
与运行时系统的交互
OC源码
NSObject方法
运行时函数
消息机制
objc_msgSend函数
[receiver message]
转变为一个消息函数的调用——objc_msgSend。函数带有接受者和方法提及的名字,作为方法选择器的两个主要参数:
objc_msgSend(receiver, selector)
任何传入消息的参数都被交给objc_msgSend:
objc_msgSend(receiver, selector, arg1, arg2, ...);
- 它首先找到选择器所引用的方法实现。由于相同的方法可以被单独的类不同地实现,它找到的确切的方法实现取决于接受者所属的类。
- 然后它通过接收对象以及该方法所指定的所有参数来调用方法实现。
- 最后它通过调用方法实现的返回值来作为自己的返回值。
- 一个指向父类的指针
- 一个类的调度表。这个表包含关联了方法选择器与它们标记的类特定方法的地址。setOrigin::方法的选择器与setOrigin::的地址关联起来,等等。
使用隐藏参数
- 接收对象
- 方法的选择器
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
self在两个参数中更有用一些,事实上,这是接收的对象的实例变量对于方法定义变得可用的方式。
得到一个方法地址
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
第一次被传过去的两个参数是接收对象self和方法选择器_cmd。
动态方法解析
这节描述了如何动态地提供一个方法。
动态方法解析
有时你会想要动态地提供一个方法的实现,例如,OC声明属性特性包含@dynamic关键字
@dynamic propertyName;
告诉编译器与属性关联的方法会被动态地提供。
你可以实现方法resolveInstanceMethod:和方法resolveClassMethod:来分别地动态提供一个给定selector,实例和类名的方法实现。
一个OC方法的根本就是带有self和_cmd两个参数的C函数,你可以用函数class_addMethod来添加一个函数到类中去作为方法。因此给出以下函数:
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
你可以使用方法resolveInstanceMethod:动态添加它的实现到类中作为方法(resolveThisMethodDynamically):
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
官方的runtime编程指南到这里就结束了,你可以喷我,但我还是想说,新手根本看不懂所以还要再增加篇幅从头讲
每个方法都有一个SEL(selector)和一个IML(implement),SEL可以随便写,但是不一定有对应的IML,如果消息函数沿着继承层次结构找到了顶端还是找不到对应的方法实现,就会抛出异常而crash。
上文提到“消息函数做了所有动态绑定所必需的事情:它首先找到选择器所引用的方法实现。”但是如果一直没有找到,就会开始尝试动态解析,消息转发,标准消息转发:
其实这就是通过SEL查找IML,这个过程也可以用下图表示:
SEL查找IML过程
resolveInstanceMethod函数
函数原型是:
+ (BOOL)resolveInstanceMethod:(SEL)name;
在运行时(runtime),SEL没有找到对应的IML就会先执行这个函数,
这个函数是给类利用class_addMethod添加方法的机会。
如果实现了添加方法的代码则返回YES,如果没有实现则返回NO。
新建一个工程在.m文件添加如下代码:
#import "ViewController.h"
@interface ViewController()
@end
@implement ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(doSomething:)];
}
- (void)didReceiveMemoryWarning{
[super didReveiceMemoryWarning];
}
结果就是程序crash控制台报错:
terminating with uncaught exceptionof type NSException
因为程序没有找到doSomething:这个方法,下面我们实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;
并且判断若果sel是doSomething:那就说出add method here
#import "ViewController.h"
@interface ViewController()
@end
@implement ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(doSomething)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == @selector(doSomething)){
NSLog(@"add method here!");
return YES;
}
return NO;
}
- (void)didReceiveMemoryWarning{
[super didReveiceMemoryWarning];
}
运行查看控制台发现程序虽然崩溃了,但是控制台输出的第一句话就是add method here! 说明确实进入了这个方法并且通过了判断。
所以我们可以在if语句里做一下操作,使得这个方法的得到实现而不至于走到方法:
- (void)doesNotRecognizeSelector:(SEL)aSelector;
走到这个方法就会Crash,接下来我们继续更改
#import "ViewController.h"
@interface ViewController()
@end
@implement ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(doSomething:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == @selector(doSomething)){
NSLog(@"add method here!");
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP(id self, SEL _cmd){
NSLog(@"doSomthing SEL");
}
- (void)didReceiveMemoryWarning{
[super didReveiceMemoryWarning];
}
定义了一个void dynamicMethodIMP(id self, SEL _cmd)这个函数,并且在+ (BOOL)resolveInstanceMethod:(SEL)sel方法中执行了class_addMethod方法,运行工程我们查看LOG:
add method here!
doSomething SEL
程序成功输入,这说明我们已经通过runtime成功向我们这个类中添加了一个方法,这里说几点
注意事项:
首先class_addMethod是定义在<objc/runtime.h>中的方法,使用前要导入头文件,前几个查找IML的方法是定义在NSObject中的方法,所以无需导入头文件。
我们再来看一下class_addMethod的方法定义
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)
- cls 方法所要添加到的类
- name 方法名字可以随意起
- imp 实现方法的函数
- types 定义该函数返回值类型和参数类型(依次按序输入)的字符串,注意这个参数不是NSString类型,而是const char*类型 所以不要用@“”,而要直接用"",我们上面的函数是 void dynamicMethod(id self, SEL _cmd) 返回值是void——(对应)v ; 第一个参数是self——(对应)@ 第二个参数是SEL——(对应):,所以连起来就是“v@:”就是此处该写入的参数。
再举个例子:
int newMethod(id self, SEL _cmd, NSString *str){
return 100;
}
那么添加这个函数的方法就是
class_addMethod([self class], SEL name, IMP imp, "i@:@");
forwardingTargetForSelector函数
如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法,消息继续往下传递到-(id)forwardingTargetForSelector:(SEL)aSelector
看看是不是有对象可以执行这个方法,我们再原有例子的基础上在新建一个类
#import"SecondViewController.h"
@interface SecondViewController
@end
@implementation SecondViewController
- (void)viewDidLoad{
[super viewDidLoad];
}
- (void)secondVCMethod{
NSLog(@"This is secondVC method");
}
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
}
添加好后我们要在ViewController 中调用secondVCMethod,可是这个两个类并没有继承关系,正常是无法调用的
在ViewController中
#import "ViewController.h"
@interface ViewController()
@end
@implement ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(secondVCMethod)];
}
- (void)didReceiveMemoryWarning{
[super didReveiceMemoryWarning];
}
这样调用肯定会找不到方法而崩溃,下面我们是用forwardingTargetForSelector方法来转发一下消息,继续处理ViewConreoller类
#import "ViewController.h"
@interface ViewController()
@end
@implement ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(secondVCMethod)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return [super resolveInstanceMethod:sel];
}
- (id)forwadingTargetForSelector:(SEL)aSelector{
Class class = NSClassFromString(@"SecondViewController");
UIViewController * vc = class.new;
if(aSelector == NSSelectorFromString(@"secondVCMethod")){
NSLog(@"secondVC do this");
return vc;
}
return nil;
}
- (void)didReceiveMemoryWarning{
[super didReveiceMemoryWarning];
}
我们会发现secondVCMethod方法执行了,程序并没有崩溃,原因在于当没有找到secondVCMethod这个方法的时候消息一直传递到方法
- (id)forwadingTargetForSelector:(SEL)aSelector
然后在里面创建了一个SecondViewController的对象,并判断如果这个需要转发的方法是secondViewController中的方法就返回secondViewController的对象,消息成功转发给secondViewController的对象,并执行。同时也相当于完成了一个多继承。
动态加载
一个OC程序可以在运行的时候绑定并连接新的类和分类。新的代码会被合并到程序中,与一开始就加载的代码没有区别。
动态加载可以被用来做血多不同的事情,例如,在系统APP的许多模块都是动态绑定。
在Cocoa环境下,动态绑定通常被用来自定义APP。其他则是用来写一些运行时加载的组件——就像Interface Builder加载定制的调色板和OS X系统应用加载自定义模块一样,可加载模块扩展了你的应用可以做什么,它们的贡献在于你提供框架,他人提供代码。
虽然运行时函数在Mach-O文件中执行动态绑定(objc_loadModiles,在objc_load.h中定义),Cocoa的NSbundle类为动态帮顶提供了一个显着更方便的接口——一种面向对象并与相关服务集成的接口。在Foundation框架查看NSBundle类的说明参考类的信息和它所使用的。通过 《OS X ABI Mach-O 文件格式参考》查看Mach-O文件的信息。
消息转发
向一个对象发送消息,对象没有处理消息,就会报错。然而,在报错之前,运行时系统给接收消息的对象两个选择去处理消息。
转发
如果你给一个对象发送消息,并且这个对象没有处理这个消息,在抛出一个错误之前,运行时系统会向对象发送一个消息:
forwardInvocation:
用
NSInvocation作为它的唯一实参,通过
NSInvocation
对象封装了原始的消息和实参,
类对象与基础数据结构
Class
typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_object{
Class isa OBJC_ISA_AVAILABILITY;
};
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
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;
isa
struct objc_object{
Class isa OBJC_ISA_AVAILABILITY;
};
是一个Class类型的指针,每个Instance都有一个isa指针,指向对象的类,也叫普通的Class,这个Class中存储普通的成员变量和对象方法,而Class类里也有一个isa指针(所有的类自身也是一个对象),指向元类meteClass,也就是静态Class,静态Class中存储static类型的成员变量和类方法。
super_class
cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};