RunTime的用法
RunTime简介##
刚入行的时候,经常听到某些自称大神的人说runtime怎么怎么强大怎么怎么牛逼,我总是被忽悠的一愣一愣。但是当你问他runtime到底是什么的时候?他就只会含含糊糊告诉你三个字:运行时。。。听到这我只能说:你说的好有道理,我竟然无言以对!!!但这个东西到底是什么?到底能用在哪里呢?下面简单讲一下自己的理解。为了不把大家搞懵逼,下面我先通过几个实例讲一下runtime到底用在什么地方。
RunTime的应用场景##
- 1.给分类添加一个属性(本质上只是添加了关联,类似于属性的作用)
当我们看一些第三方框架的时候我们可能看到这样的代码,例如在SDWebImage中
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, &imageURLKey);
SDWebImage是UIImageView的一个分类。我们可以使用方法sd_setImageWithURL根据URL去下载图片,但是如果我们的URL地址在后期要用到,我们如何把URL保存在下来呢?我们都知道通过分类可以添加方法,但是添加属性就无能为力了。这个时候我们的runtime就派上用场了,通过上边的两个函数,我们就能动态的为我们的分类添加属性了。通过objc_setAssociatedObject就能把URL 保存下来,在需要用到的时候通过objc_getAssociatedObject取出URL的值。
- 2.动态的交换方法的实现
大家可能看到过runtime中有这么一个函数method_exchangeImplementations,它能在运行时动态的交换两个方法的具体实现。
+ (void)load {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
但是具体可以用在哪里呢?之前看到一种非常有趣的做法:在我们做项目的后期,我们可能需要在各个页面加上统计事件,大部分时候是通过在每个控制器的viewDidAppear和viewDidDisappear等方法中加入统计。如果我们的项目比较大,加起来就比较麻烦,但是通过runtime我们可以轻松解决这个问题。首先我们需要写一个方法,在此方法中加入统计事件,然后在UIViewController的load方法中通过method_exchangeImplementations交换viewDidAppear的实现,这样我们就能轻松解决这个问题。添加页面的统计以及点击事件的统计可以参考下边这篇文章,作者写的非常棒:http://www.cocoachina.com/ios/20160421/15912.html
- 3.通过runtime自动实现归档
在开发中,我们很多时候需要对数据进行归档,但是如果一个Model中的属性比较多的时候,我们可能会被搞得头昏脑涨,十分影响我们的开发效率。这个时候我们可以通过runtime来帮我们解决这个问题。大致实现如下:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *vars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
const char *name = ivar_getName(vars[i]);
NSString *strName = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:strName];
[self setValue:value forKey:strName];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *vars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
const char *name = ivar_getName(vars[i]);
NSString *strName = [NSString stringWithUTF8String:name];
id value = [self valueForKey:strName];
[aCoder encodeObject:value forKey:strName];
}
}
```
* 4.通过runtime实现字典转模型
在iOS开发中,我们可能会用到各种字典转模型的框架,如:JSONModel/MJExtension等。其实这些字典转模型的框架正是利用了runtime的特性。首先通过class_copyIvarList和ivar_getName获取属性名称,然后在字典中查找到对应的值,最后通过KVC为model的属性赋值。大致过程如下:
```
+ (void)modelWithDict:(NSDictionary *)dict {
id objc = [[self alloc] init];
unsigned int count;
// 获取model的所有属性
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 获取成员属性名
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
// 去掉name里的第一个下滑线字符
NSString *key = [name substringFromIndex:1];
// 从字典中查找对应属性的值
id value = dict[key];
[objc setValue:value forKey:key];
}
}
```
总结:runtime的使用场景还有动态的给类添加方法、发送消息等等,但个人觉得比较实用的有以上几种,当然更多的使用的场合和技巧还要靠大家来发掘。
##RunTime能做什么?
runtime能做什么呢?其实我们只要在runtime.h这个头文件中查看它所提供的API就知道了,下面只是列出一些我们常用的runtime API。
```
//获取cls类对象所有成员ivar结构体
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//获取cls类对象name对应的实例方法结构体
Method class_getInstanceMethod(Class cls, SEL name)
//获取cls类对象name对应类方法结构体
Method class_getClassMethod(Class cls, SEL name)
//获取cls类对象name对应方法imp实现
IMP class_getMethodImplementation(Class cls, SEL name)
//测试cls对应的实例是否响应sel对应的方法
BOOL class_respondsToSelector(Class cls, SEL sel)
//获取cls对应方法列表
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//测试cls是否遵守protocol协议
BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
//为cls类对象添加新方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//替换cls类对象中name对应方法的实现
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//为cls添加新成员
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
//为cls添加新属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
//获取m对应的选择器
SEL method_getName(Method m)
//获取m对应的方法实现的imp指针
IMP method_getImplementation(Method m)
//获取m方法的对应编码
const char *method_getTypeEncoding(Method m)
//获取m方法参数的个数
unsigned int method_getNumberOfArguments(Method m)
//copy方法返回值类型
char *method_copyReturnType(Method m)
//获取m方法index索引参数的类型
char *method_copyArgumentType(Method m, unsigned int index)
//获取m方法返回值类型
void method_getReturnType(Method m, char *dst, size_t dst_len)
//获取方法的参数类型
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len)
//设置m方法的具体实现指针
IMP method_setImplementation(Method m, IMP imp)
//交换m1,m2方法对应具体实现的函数指针
void method_exchangeImplementations(Method m1, Method m2)
//获取v的名称
const char *ivar_getName(Ivar v)
//获取v的类型编码
const char *ivar_getTypeEncoding(Ivar v)
//设置object对象关联的对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取object关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除object关联的对象
void objc_removeAssociatedObjects(id object)
```
##到底什么是RunTime?
就自己的理解而言,runtime就是一套底层的C语言API,我们的程序在运行的时候会将我们编写的OC代码转为底层的C语言函数来执行。于是我们可以利用runtime直接调用这些C语言的函数,这样我们可以在运行时动态的修改类的具体实现等。