什么是runtime?
1> runtime是一套底层的C语言API(包含很多强大实用的C语言数据类型、C语言函数)
2> 实际上,平时我们编写的OC代码,底层都是基于runtime实现的
- 也就是说,平时我们编写的OC代码,最终都是转成了底层的runtime代码(C语言代码)
runtime有啥用?
1> 能动态产生一个类、一个成员变量、一个方法
2> 能动态修改一个类、一个成员变量、一个方法
3> 能动态删除一个类、一个成员变量、一个方法
常见的函数、头文件
成员变量、类、方法
#import
获得某个类内部的所有成员变量
Ivar * class_copyIvarList
获得某个类内部的所有方法
Method * class_copyMethodList
获得某个实例方法(对象方法,减号-开头)
Method class_getInstanceMethod
获得某个类方法(加号+开头)
Method class_getClassMethod
交换2个方法的具体实现
method_exchangeImplementations
消息机制
#import
- @param receiver 消息接收者
- @param selector 消息对应的方法名字
- @param arg1.arg2...消息中的任意数目的参数
objc_msgSend(receiver, selector, arg1, arg2, ...)
获取某个类的所有成员变量(延伸作用是归档、解档)
- (void)testRuntimeIvar
{
// Ivar : 成员变量
unsigned int count = 0;
// 获得所有的成员变量
//指针可以理解成为数组(根据1、2、3进行取值)
Ivar *ivars = class_copyIvarList([HMPerson class], &count);
for (int i = 0; i
归档示例
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([HMPerson class], &count);
for (int i = 0; i
交换两个方法的实现
交换方法
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)
获得方法
Method class_getClassMethod(Class cls, SEL name)
例:
分类加载进内存的时候会调用
load
方法,只会调用一次。当使用这个方法的时候,不需要声明也不需要写.h
方法。因为所有.m
方法都会加入内存中,调用到load
中。实现了
imageWithName:
和imageNamed:
方法的交换,需要注意的问题是再重写imageWithName:
时候不要在内部使用imageNamed:
方法
#import
@implementation UIImage (Extension)
/**
* 只要分类被装载到内存中,就会调用1次
*/
+ (void)load
{
Method otherMehtod = class_getClassMethod(self, @selector(imageWithName:));
Method originMehtod = class_getClassMethod(self, @selector(imageNamed:));
// 交换2个方法的实现
method_exchangeImplementations(otherMehtod, originMehtod);
}
+ (UIImage *)imageWithName:(NSString *)name
{
BOOL iOS7 = [[UIDevice currentDevice].systemVersion floatValue] >= 7.0;
UIImage *image = nil;
if (iOS7) {
NSString *newName = [name stringByAppendingString:@"_os7"];
image = [UIImage imageWithName:newName];
}
if (image == nil) {
image = [UIImage imageWithName:name];
}
return image;
}
获得方法地址
- 编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
避免动态绑定的唯一办法就是取得方法的地址,并且直接象函数调用一样调用它。当一个方法会被连续调 用很多次,而且您希望节省每次调用方法都要发送消息的开销时,使用方法地址来调用方法就显得很有效。
利用 Cocoa 运行时系统的提供的功能 methodForSelector:
实现某个方法
void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled)];
for (NSInteger i = 0; i < 100; i++) {
setter(target,@selector(setFilled),YES);
}
- (void)setFilled
{
NSLog(@"123");
}
使用 methodForSelector:
来避免动态绑定将减少大部分消息的开销,但是这只有在指定的消息被重 复发送很多次时才有意义,例如上面的 for
循环。
注意,methodForSelector:
是 Cocoa
运行时系统的提供的功能,而不是Objective-C
语言本身的功 能。
消息机制
当对象收到消息时,消息函数首先根据该对象的isa指针找到该对象所对应的类的方法表,并从表中寻找 该消息对应的方法选标。如果找不到,objc_msgSend 将继续从父类中寻找,直到 NSObject 类。一 旦找到了方法选标, objc_msgSend 则以消息接收者对象为参数调用,调用该选标对应的方法实现。
这就是在运行时系统中选择方法实现的方式。在面向对象编程中,一般称作方法和消息动态绑定的过程。
为了加快消息的处理过程,运行时系统通常会将使用过的方法选标和方法实现的地址放入缓存中。每个类
都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。消息函数会首先检查消息接收者对象
对应的类的缓存(理论上,如果一个方法被使用过一次,那么它很可能被再次使用)。如果在缓存中已经
有了需要的方法选标,则消息仅仅比函数调用慢一点点。如果程序运行了足够长的时间,几乎每个消息都
能在缓存中找到方法实现。程序运行时,缓存也将随着新的消息的增加而增加。
消息转发
如果一个对象收到一条无法处理的消息,运行时系统会在抛出错误前,给该对象发送一条
forwardInvocation:
消息,该消息的唯一参数是个 NSInvocation
类型的对象——该对象封装了 原始的消息和消息的参数。
防止crash
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
//Person实现了`invocation selector`方法
return [Person instanceMethodSignatureForSelector:aSelector];
//传递参数到forwardInvocation: 的invocation的key,采用的是传递方式的办法
// return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
Person *person = [[Person alloc] init];
//
// NSString *key = NSStringFromSelector([invocation selector]);
NSString *sel = NSStringFromSelector([invocation selector]);
NSLog(@"%@",sel);
if ([person respondsToSelector:
[invocation selector]])
[invocation invokeWithTarget:person];
else
NSLog(@"nothing");
//程序会crash
// [super forwardInvocation:invocation]
}
参考资料:
链接: [http://pan.baidu.com/s/1i3JB56d][jekyll - DB] 密码: cmh6
[jekyll - DB]: http://pan.baidu.com/s/1i3JB56d