一、Runtime简介
- Runtime简称运行时, OC就是运行时机制, 也就是在运行的时候的一些机制, 最主要的的是消息机制
- 对于C语言, 函数的调用在编译阶段的时候就会决定调用哪个函数
- 对于OC, 属于动态调用过程, 在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到相应的函数来调用
- 事实证明
在编译阶段, OC可以调用任何函数, 即使这个函数并没有实现,只要声明过就不会报错
在编译阶段,C语言调用未实现的函数会报错
二、Runtime应用
Xcode配置
从Xcode6苹果不推荐用runtime, 如果项目中使用runtime, 在xcode中做如下配置:
找到buildsetting
-> 搜索msg
(选中All
) -> 找到 Enable Strict Checking of objs_msgSend Call 设置为 NO
设置完成后就可以使用底层Runtime编程了
Runtime消息机制
在前一篇Runtime基础中已经知道, OC的方法调用,其实每次都是一个运行时消息发送过程。
objc_msgSend(receiver, selector)
想要使用Runtime, 必须导入头文件objc/message.h
#import <objc/message.h>
交换方法
应用场景: 当第三方框架 或者 系统原生方法功能不能满足我们需求的时候,我们可以在保持系统原有方法功能的基础上,添加额外的功能。
需求: 加载本地图片用系统方法[UIImage imageNamed:@"image"];
是无法知道到底有没有加载成功。给系统的imageNamed
添加额外功能(是否成功加载图片。
方法一: 自定义继承系统UIImage的类,重写方法。 弊端是每次都要导入头文件, 项目太大, 没办法实现。
方法二: runtime交换方法
1. 给系统的方法添加分类
UIImage+LVDImage.h
UIImage+LVDImage.m
2. 自己实现一个带有扩展功能的方法
+ (UIImage *)lvd_imageNamed:(NSString *)name
{
// UIImage *image = [UIImage imageNamed:name];
/* 交换方法后 imageNamed => lvd_imageNamed
此处使用imageNamed就是调用lvd_imageNamed, 会陷入死循环
*/
UIImage *image = [UIImage lvd_imageNamed:name];
/* 交换方法后 lvd_imageNamed => imageNamed
此处使用lvd_imageNamed, 就是调用系统方法imageNamed
*/
if (image) {
NSLog(@"图片加载成功");
}
else {
NSLog(@"图片加载失败");
}
return image;
}
3. 使用Runtime交换方法
// 把类加载进内存的时候调用 , swift中没有
+ (void)load
{
// 获取imageNamed
Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
// 获取lvd_imageNamed
Method lvd_imageNameMethod = class_getClassMethod(self, @selector(lvd_imageNamed:));
// 交换方法
method_exchangeImplementations(imageNameMethod, lvd_imageNameMethod);
}
// 会调用多次, swift 中使用
//+ (void)initialize
//{
// // 使用 dispatch_once 只调用一次
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
// // 交换方法操作
// });
//}
class_getClassMethod()
获取某个类的方法
method_exchangeImplementations()
交换方法实现方式
4.看下交换结果
UIImage *img = [UIImage imageNamed:@"red_tick.png"];
2018-09-08 20:47:35.287603+0800 Runtime[14303:2376510] 图片加载成功
注意
因为做了方法交换, 调用 imageNamed => lvd_imageNamed,调用 lvd_imageNamed => imageNamed, 不要出现死循环
动态添加方法
OC都是懒加载机制, 只要这个方法实现了, 就会马上添加到方法列表中。
需求: runtime 动态添加方法处理调用一个未实现的方法 和去除报错。
1. 调用未实现方法
创建一个Animal
类, 直接调用run
方法 (run方法未声明, 也未实现)
Animal *pig = [[Animal alloc] init];
[pig performSelector:@selector(run)];
由于run方法未声明, 为了能编译通过, 使用performSelector调用方法, 运行时才执行。运行一下, 果然崩溃报错
reason: '-[Animal run]: unrecognized selector sent to instance 0x6000000063d0'
2. 使用Runtime动态添加方法
// 两个参数self和_cmd,是默认的,其实苹果每个函数调用都会传self和_cmd,
// 只是编译器帮我们做了.
void run (id self, SEL _cmd)
{
NSLog(@"小猪在跑");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 实现run方法
if (sel == NSSelectorFromString(@"run")) {
class_addMethod([self class], sel, (IMP)run, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
不存在实例方法时,会调用这个方法
class_addMethod()
给某个类添加方法
从Xcode打开苹果开文档
(Help -> Developer Documentation) 找到 运行时 class_addMethod
函数
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
-
Class :
给哪个类添加方法 -
SEL :
添加的方法 -
IMP :
方法实现 => 函数 => 函数入口 => 函数名 -
types :
方法返回类型-
v
表示 返回值void -
@
表示 参数id (an object type) -
:
表示 参数sel (A method selector)
更多types 参考 Type Encodings
-
运行看下, 方法动态添加成功
2018-09-08 22:42:08.583909+0800 Runtime[15412:2538210] 小猪在跑
如果方法带参数呢, 如[pig performSelector:@selector(run) withObject:@20];
动态添方法如下
// 带参数
void run (id self, SEL _cmd, NSNumber *n)
{
NSLog(@"小猪跑了%@米", n);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@", NSStringFromSelector(sel));
// 实现run方法
if (sel == NSSelectorFromString(@"run")) {
/*
Class : 给那个类添加方法
SEL : 添加哪个方法
IMP : 方法实现 => 函数 => 函数入口 => 函数名
types : 方法类型
*/
// 没参数
// class_addMethod([self class], sel, (IMP)run, "v@:");
// 有参数
class_addMethod([self class], sel, (IMP)run, "v@:@");
return YES;
}
return [super resolveClassMethod:sel];
}
动态添加属性
需求让一个NSObject类, 保存一个字符串
也可以理解为, 给category添加属性, 让某个属性和对象关联。 在我的另一篇文章 @property中的关键字 已经实现给分类添加属性
注:
类别中使用@property只会生成setter, getter方法声明,不会生成方法实现,也不会生成带下划线的成员变量。
1. 给NSObject添加分类
NSObject+Property.h
NSObject+Property.m
2. 在分类中声明属性
@interface NSObject (Property)
@property NSString *name;
@end
3. 将属性和NSObject对象关联
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
字典转模型
-
KVC:
模型中的属性必须与字典中的key一一对应。手动声明属性太麻烦。 - 优秀的
JSON转模型
第三方库JSONModel、YYModel等都利用runtime对属性进行获取,赋值等操作,要比KVC进行模型转换更加强大,更有效率。
1. 动态添加属性
创建NSDictionary, 实现一个方法自动生成属性代码
- (void)createPropertyCode
{
NSMutableString *codes = [NSMutableString string];
// 遍历字典
[self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 生成属性
NSString *code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;", key];
[codes appendFormat:@"\n%n\n", code];
}];
NSLog(@"property: \n %@", codes);
}
用NSDictionary对象直接调用createPropertyWithDict方法,
将打印的codes复制到模型即可。
@property (nonatomic, strong) NSString *status;
@property (nonatomic, strong) NSString *memo;
@property (nonatomic, assign) NSInteger money;
@property (nonatomic, assign) BOOL flag;
@property (nonatomic, strong) NSString *name;
2. 字典转换成模型
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
NSObject *objc = [[[self class] alloc] init];
unsigned int count = 0;
// 获取Model中的所有成员变量 Ivar成员变量数组
//之所以用class_copyIvarList 而不用 class_copyPropertyList,因为我们可能忽略成员变量,但不会忽略属性
Ivar *ivarList = class_copyIvarList([self class], &count);
// 遍历所有的成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量
Ivar ivar = ivarList[i];
// 获取成员变量名字 (c语言字符串转oc字符串转 -- utf-8)
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 去掉下划线
NSString *key = [ivarName substringFromIndex:1];
// 去字典中查找对应的value
id value = dict[key];
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
以上就是几个runtime应用场景, Runtime底层实现还有很多, 可以根据项目需求谨慎仔细的使用。