一 、Runtime 简介
Runtime 简称运行时,是系统在运行的时候的一些机制,其中最主要的是消息机制。它是一套比较底层的纯 C 语言 API, 属于一个 C 语言库,包含了很多底层的 C 语言 API。我们平时编写的 OC 代码,在程序运行过程时,其实最终都是转成了 runtime 的 C 语言代码。如下所示:
// OC代码:
[Person coding];
//运行时 runtime 会将它转化成 C 语言的代码:
objc_msgSend(Person, @selector(coding));
二 、相关函数
// 遍历某个类所有的成员变量
class_copyIvarList
// 遍历某个类所有的方法
class_copyMethodList
// 获取指定名称的成员变量
class_getInstanceVariable
// 获取成员变量名
ivar_getName
// 获取成员变量类型编码
ivar_getTypeEncoding
// 获取某个对象成员变量的值
object_getIvar
// 设置某个对象成员变量的值
object_setIvar
// 给对象发送消息
objc_msgSend
三 、相关应用
- 更改属性值
- 动态添加属性
- 动态添加方法
- 交换方法的实现
- 拦截并替换方法
- 在方法上增加额外功能
四 、代码实现
要使用Runtime,要先引入头文件#import <objc/runtime.h>
1. 用 runtime 修改一个对象的属性值
- (void)updatePropertyValue {
unsigned int count = 0;
// 动态获取类中的所有属性(包括私有)
Ivar *ivar = class_copyIvarList(_person.class, &count);
// 遍历属性找到对应字段
for (int i=0; i<count; i++) {
Ivar tempIvar = ivar[i];
const char *varChar = ivar_getName(tempIvar);
NSString *varStr = [NSString stringWithUTF8String:varChar];
if ([varStr isEqualToString:@"_name"]) {
// 修改对应的字段值
object_setIvar(_person, tempIvar, @"更改属性值成功");
break;
}
}
NSLog(@">>>>>person.name:%@",_person.name);
}
2. 动态添加属性
用 Runtime 为一个类添加属性, iOS 分类里一般会这样用, 我们建立一个分类, Person+Property.h, 并添加以下代码:
#import "Person.h"
@interface Person (Property)
- (NSInteger)age;
- (void)setAge:(NSInteger)age
@end
#import "Person+Property.h"
#import <objc/runtime.h>
@implementation Person (Property)
- (NSInteger)age {
return [objc_getAssociatedObject(self, @"age") integerValue];
}
-(void)setAge:(NSInteger)age{
objc_setAssociatedObject(self, @"age", @(age), OBJC_ASSOCIATION_ASSIGN);
}
@end
这样只要引用Person+Property.h, 用 NSObject 创建的对象就会有一个 age 属性, 我们可以直接这样写:
_person.age = 29;
NSLog(@">>>>>person.age:%@",@(_person.age));
3. 动态添加方法
person 类中没有 coding 方法,我们用 runtime 给 person 类添加了一个名字叫 coding 的方法,最终再调用coding方法做出相应. 下面代码的几个参数需要注意一下:
- (void)addMethod {
/*
动态添加 coding 方法
(IMP)codingOC 意思是 codingOC 的地址指针;
"v@:" 意思是,v 代表无返回值 void,
如果是 i 则代表 int;
@ 代表 id sel;
: 代表 SEL _cmd;
“v@:@@” 意思是,两个参数的没有返回值。
*/
class_addMethod([_person class], @selector(coding), (IMP)codingOC, "v@:");
if ([_person respondsToSelector:@selector(coding)]) {
[_person performSelector:@selector(coding)];
NSLog(@">>>>>:添加方法成功");
} else {
NSLog(@">>>>>:添加方法失败");
}
}
//编写 codingOC 的实现
void codingOC(id self, SEL _cmd) {
NSLog(@"执行方法内容");
}
4. 交换方法的实现
某个类有两个方法, 比如 person 类有两个方法, eat 方法与 sleep 方法, 我们用 runtime 交换一下这两个方法, 就会出现这样的情况, 当我们调用 eat 的时候, 执行的是 sleep, 当我们调用 sleep 的时候, 执行的是 eat。
- (void)exchangeImplementations {
NSLog(@">>>>>交换前");
[_person eat];
[_person sleep];
Method oriMethod = class_getInstanceMethod([_person class], @selector(eat));
Method curMethod = class_getInstanceMethod([_person class], @selector(sleep));
method_exchangeImplementations(oriMethod, curMethod);
NSLog(@">>>>>交换后");
[_person eat];
[_person sleep];
}
5. 拦截并替换方法
这个功能和上面的其实有些类似, 拦截并替换方法可以拦截并替换同一个类的, 也可以在两个类之间进行, 我这里用了两个不同的类, 下面是简单的代码实现.
- (void)interceptAndExchange {
Person *person = [Person new];
Dog *dog = [Dog new];
NSLog(@">>>>>拦截前");
[person sleep];
Method oriMethod = class_getInstanceMethod([person class], @selector(sleep));
Method curMethod = class_getInstanceMethod([dog class], @selector(sleep));
method_exchangeImplementations(oriMethod, curMethod);
NSLog(@">>>>>拦截后");
[person sleep];
}
6. 在方法上增加额外功能
这个使用场景还是挺多的, 比如我们需要记录 APP 中某一个按钮的点击次数, 这个时候我们便可以利用 runtime 来实现这个功能. 我这里写了个 UIButton 的子类, 然后在 + (void)load 中用 runtime 给它增加了一个功能, 核心代码如下:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(self.class, @selector(sendAction:to:forEvent:));
Method cusMethod = class_getInstanceMethod(self.class, @selector(customSendAction:to:forEvent:));
// 判断自定义的方法是否实现, 避免崩溃
BOOL addSuccess = class_addMethod(self.class, @selector(sendAction:to:forEvent:), method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSuccess) {
// 没有实现, 将源方法的实现替换到交换方法的实现
class_replaceMethod(self.class, @selector(customSendAction:to:forEvent:), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
// 已经实现, 直接交换方法
method_exchangeImplementations(oriMethod, cusMethod);
}
});
}
好吧,先就到这里
更多请参考 iOS 开发中 runtime 常用的几种方法
基础知识请参考iOS开发·runtime原理与实践: 基本知识篇
上一篇 RunLoop入门
网友评论