首先看看Runtime是什么?
Runtime其实是一套由C语言API组合成的库,它会尽可能的把代码的决策过程推迟到运行时。
对于C语言,函数的调用在编译的时候会决定调用哪个函数。
对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。OC代码最终会转换成底层Runtime的代码,在运行时来根据函数名称找对应的函数。
我们接下来看看Runtime基本可以做些什么事:
- 可以动态的获取或创建类;
- 可以动态的为一个类增加属性和方法;
- 能够在程序运行时遍历类中的实例变量,属性或方法
- 能够调换两个方法的逻辑实现。
接下来一个一个的看运用runtime如何实现上述那些事儿。
首先用OC创建一个Person类来模拟:
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *sex;
+ (instancetype)person;
- (void)eat;
@end
然后在用到的地方输入以下代码:
Person *person = [Person person];
NSLog(@"%@",[Person class]);
Class personClass = person.class;
unsigned int outCount = 0;
//遍历实例变量
Ivar *ivarPtr = class_copyIvarList(personClass, &outCount);
for (int i=0; i<outCount; i++) {
Ivar ivar = ivarPtr[i];
NSLog(@"实例变量:%s",ivar_getName(ivar));
}
//遍历属性
objc_property_t *propertyPtr = class_copyPropertyList(personClass, &outCount);
for (int i=0; i<outCount; i++) {
objc_property_t property = propertyPtr[i];
NSLog(@"属性:%s",property_getName(property));
}
//遍历对象方法
Method *methodPtr = class_copyMethodList(personClass, &outCount);
for (int i=0; i<outCount; i++) {
Method method = methodPtr[i];
SEL selector = method_getName(method);
NSLog(@"%@",NSStringFromSelector(selector));
}
通过以上runtime里的方法,我们能获取到一个类的各种实例方法等。我们来看看runtime.h中的东西:
可以看到,我们用到的方法些全是结构体指针,上面也已经有解释,那些结构体分别代表类方法,实例变量,类别,OC声明的属性。runtime.h中还有很多结构体和函数,感兴趣的可以看一看。而且runtime是开源的,能够在苹果开发者网站下到它们的源码。
现在运行一下上面的程序,可以看到对应的属性,方法些都打印出来了:
最后一个是隐藏的函数,和对象的销毁有关。
接着看看我们runtime获取OC里面类的方法来执行,不过要先配置一些,到工程中,在Build Settings中,搜索objc,然后将下面的那个yes给改为no就可以在工程中用objc_msgSend了,不然编译器会报错,阻止用objc_msgSend。
这样后,在代码中输入:
[person setName:@"张三"];
NSLog(@"%@",person.name);
//
objc_msgSend(person,@selector(setName:),@"李四");
NSLog(@"%@",person.name);
打印输出:
由此我们看到,runtime能够调用某对象的某个方法来执行。其实在程序运行时,上面那个OC代码就是转化为后面那个函数来运行的。
调换两个方法的逻辑实现
runtime的这个作用在实际使用之中还用的比较多。它的这个作业可以将原本框架中本来无法修改的方法指定为我们自己定义的方法,有时候确实蛮方便的。
比如我们在用UIViewController比较多的情况下,界面跳转往往会忘记自己现在所处的界面对应的是哪个Controller控制,因此有时候想在进入每个界面的时候,能自动打印出当前的Controller,就可以用runtime交换方法来实现。
比如我们先建一个UIViewController的类别:UIViewController+Log.
.h代码:
@interface UIViewController (Log)
@end
.m代码:
@implementation UIViewController (Log)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
id obj = [[self alloc] init];
[obj swizzleMethod:@selector(viewDidLoad) withMethod:@selector(logViewDidLoad)];
});
}
- (void)logViewDidLoad{
NSLog(@"你现在在:%s",object_getClassName(self));
[self logViewDidLoad];
}
- (void)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, origSelector);
Method swizzledMethod = class_getInstanceMethod(class, newSelector);
BOOL didAddMethod = class_addMethod(class,origSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,newSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
我们看到,要交换两个方法,简单点来说就是获取到方法指针,然后调用method_exchangeImplementations函数,就可以将两个指针指向的方法交换,也就是两个指针分别指向了另一个方法的地址。
这里写了一点runtime基本的东西,另外看到一篇介绍runtime的文章,感觉蛮好的,放在这里:
让你快速上手Runtime