Runtime常用的几个场景
1.给分类动态添加属性
在FDFullscreenPopGesture中给UIViewController的分类里有这么一个属性:
@property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;
这是一个block的属性,block定义如下:
typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);
看到这里也许你会提问,OC中不是不能给分类添加属性么?正常情况下,OC是不允许给OC添加属性的。但是利用Runtime的特性,这是可以办到的。实现方法如下:
- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
{
return objc_getAssociatedObject(self, _cmd);// 根据关联的key,获取关联的值。这里的key等于_cmd,_cmd等于fd_willAppearInjectBlock
}
- (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);//关联对象
}
动态给分类添加属性的方法是:
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
获取这个属性的方法是:
objc_getAssociatedObject(id object, const void *key)
还有一个方法是移除属性:
objc_removeAssociatedObjects(id object)
是的,这样就动态的给UIViewController的分类添加了fd_willAppearInjectBlock这么一个属性。
NOTE:在使用Runtime的这些方法的时候不要忘了导入
objc/runtime.h这个头文件哦!
2.动态添加方法
要想动态添加方法我们必须了解方法是如何执行的,通常我们调用方法是通过[object message]这种方法,除了这种方法还有一种是比较少用的,就是[object performSelector:@selector(message)]这种方式。通过下面这张图我们可以了解一下他们对消息的处理的不同之处。
通过上图,我们可以得知,要想动态添加方法必须是通过[object performSelector:@selector(message)]这种方式调用方法才能在运行时阶段通过Runtime的一些方法达到动态的添加方法。如果现在有一个Person类,在其它地方通过performSelector的方式调用Person的run方法。但是Person类中并没有实现这个方法。
Person p = [Person alloc] init];
// 这个时候即使Person类没有实现run方法编译器也不会报错
[p performSelector:@selector(run)];
这时候只需要在Person中实现resolveInstanceMethod:方法就可以达到动态添加方法的目的。
//首先我们要在Person类里面实现我们要动态添加的方法
// 要注意,默认方法都有两个隐式参数
void run(id self,SEL sel){
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//先判断一下传过来的是不是run方法
if (sel == @selector(run)){
//如果是run方法就动态添加run方法
class_addMethod(self.class, @selector(run),(IMP)run, "v@:");
// 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址),如果是OC方法
//可以用+(IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
// 第四个参数:方法的签名,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
}
}
这样就达到了给一个类动态添加方法的效果了,如果想把方法转发给其他的类实现,需要处理消息转发的第二或第三个函数了。
3.替换系统自带的方法
当一些时候,系统自带效果满足不了我们的时候,要么我们自定义,要么直接替换系统的方法。在公有的API是没有方法办到的。我们来看一段FDFullscreenPopGesture的代码(注释是我加的):
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
//获取系统方法的SEL
SEL originalSelector = @selector(viewWillAppear:);
//获取替换方法的SEL
SEL swizzledSelector = @selector(fd_viewWillAppear:);
//为了获取IMP指针,获得方法的Method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//为了安全起见,先判断是否已经存在要交换的方法
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)fd_viewWillAppear:(BOOL)animated
{
//不要认为这句代码有错,其实很好理解,在调用这句的时候方法已经交换了
// Forward to primary implementation.
[self fd_viewWillAppear:animated];
if (self.fd_willAppearInjectBlock) {
self.fd_willAppearInjectBlock(self, animated);
}
}
通过上面的代码我们可以看出来,替换系统自带的方式实现需要用到的重要方法是method_exchangeImplementations()方法,并且要注意替换方法里面对自己的调用。这个方法也就是人们常说的Method Swizzling黑魔法,用的时候要注意,这是一把双刃剑!
结尾
Runtime在项目中很少用,但是要理解它,理解了之后用起来也不危险。如果你喜欢我的文章,不妨扫一扫下面的二维码请我喝杯茶。祝大家在iOS开发的道路上玩得愉快!
Runtime常用的几个场景的更多相关文章
- Runtime常用的几个应用场景
Runtime常见的几个应用场景. Runtime常见应用场景 具体应用拦截系统自带的方法调用(Method Swizzling黑魔法) 实现给分类增加属性 实现字典的模型和自动转换 JSPatch替 ...
- iOS开发之Runtime常用示例总结
经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有Runtime的相关博客,之前还真没正儿八经的总结过.之前只是在解析第三方框架源码时,聊过一些用法,也就是这些第三方框架中用到的Runt ...
- Java 常用List集合使用场景分析
Java 常用List集合使用场景分析 过年前的最后一篇,本章通过介绍ArrayList,LinkedList,Vector,CopyOnWriteArrayList 底层实现原理和四个集合的区别.让 ...
- redis五种数据类型和常用命令及适用场景
一.redis的5种数据类型: 1.基础理解: string 字符串(可以为整形.浮点型和字符串,统称为元素) list 列表(实现队列,元素不唯一,先入先出原则) set 集合(各不相同的元素) h ...
- jedis实现操纵redis的常用api及使用场景
简单记录一下,和描述一下常用的业务场景.好记性不如烂笔头. pom.xml <!--整合redis--> <dependency> <groupId>redis.c ...
- php数组去重、魔术方法、redis常用数据结构及应用场景
一.用函数对数组进行去重的方法 1.arrau_unique函数的作用 移除数组中重复的值. 将值作为字符串进行排序,然后保留每个值第一次出现的健名,健名保留不变. 第二个参数可以选择排序方式: SO ...
- Redis常用数据类型及使用场景
Redis最为常用的数据类型 字符串(String) 字符串列表(list) 字符串集合(set) 哈希(hash) 有序的字符串集合(sorted set) String(字符串) 字符串是最基本的 ...
- Redis数据类型的常用API以及使用场景
一.通用命令 1.keys 遍历出所有的key 一般不在生产环境使用 2.dbsize key的总数 3.exists key 4.del key 删除指定key-value 5.expire k ...
- Unity学习笔记(3):一些常用API和应用场景
Mathf.Lerp(float a,float b,float t)插值函数,当a < b时往a中插入t,以此来实现颜色,声音等渐变效果. GameObject.FindWithTag(str ...
随机推荐
- AS3获得当前文件的文件名
//当前文件的完整路径var fileUrl:String = this.loaderInfo.url;//查找路径的最后一个"/"var flag:int = fileUrl.l ...
- Spark sql 在yarn-cluster模式下找不到表
在hive里建一个数据库test,在数据库里建了一张表user,然后在Spark程序中使用Spark sql读取这张表 "select * form test.user" 当部署模 ...
- Vue note
1.npm run build 时,font:xx/xx "xxxx" 这种样式打包后会无效,只能写成font-size:xxx; line-height:xxx; font-fa ...
- tomcat 配置细节
1.对于url中文乱码问题: (有时进行url编码也不能能够解决,找到server.xml添加URIEncoding="UTF-8",就可以了) tomcat配置编码: ...
- Linux内核d_path函数应用的经验总结
问题背景 一个内核模块中,需要通过d_path接口获取文件的路径,然后与目标文件白名单做匹配. 在生产环境中,获取的文件是存在的,但是与文件白名单中的文件总是匹配失败. 问题定位: 通过打印d_pat ...
- 第一次oo博客作业--表达式求导
(1)说实话我这部分真的不知道写些什么,因为我只有第三次作业写了两个类,前两次都是一个类,一个类的好处可能也就是写起来比较方便(不用抽象什么共性了,直接c语言莽过去),缺点很多,架构不清晰,可读性不高 ...
- jupyter notebook 代码自动补齐插件
安装: . pip install jupyter_contrib_nbextensions -i https://pypi.mirrors.ustc.edu.cn/simple 2. jupyter ...
- MySQL编码不一致导致查询结果为空
升级数据库后(5.1到8.0),发现一个奇怪的问题,某些页面在升级前可以正常查询,但升级后什么也查不出来了,有时候还会查出错误的结果.经过一整天的排查,终于发现由两个原因导致,现记录如下. 第一是数据 ...
- List和符号分隔的字符串互相转换
一.将逗号分隔的字符串转换成List: 1. 使用JDK的Arrays类: import java.util.Arrays; import java.util.List; public class T ...
- vue全局API
一.Vue.extend() 顾名思义 extend 继承,官方给出的解释是 (使用基础 Vue 构造器,创建一个“子类”.参数是一个包含组件选项的对象.) Vue构造器是指 vue是一个构 ...