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 ...
随机推荐
- django APPEND_SLASH
#设置项是否开启URL访问地址后面不为/跳转至带有/的路径APPEND_SLASH=True
- Hash算法总结(转)
1. Hash是什么,它的作用先举个例子.我们每个活在世上的人,为了能够参与各种社会活动,都需要一个用于识别自己的标志.也许你觉得名字或是身份证就足以代表你这个人,但是这种代表性非常脆弱,因为重名的人 ...
- logstash 切分tomcat日志
以下配置是logstash切分tomcat catalina.out日志. http://grok.qiexun.net/ 分割时先用这个网站测试下语句对不对,能不能按需切割日志. input { ...
- EMF32名词解释
(EFM32)32位节能微控制器(Energy Friendly Microcontroller 32-bit) (Gecko)壁虎 (Starter Kit)入门套件 (STK)入门套件 (Debu ...
- 三个猜数字游戏代码(Python)
def binary_search(list,item): low = 0 high = len(list)-1 while low <= high: mid = (low + high)//2 ...
- Android Spannable为同一TextView设直不同样式
/** * UNICODE * <p> * 偶尔吃(1-2次/周) ( 中文破弧 * 经常吃(3-5次/周) ( 英文破弧 * * @param name * @return */ pri ...
- python中re正则表达式
1.re匹配的语法 re.math 从头开始匹配,没有匹配到返回None re.seach 匹配包含,,没有匹配到返回None re.findall 把所有匹配到的字符,以列表的形式返回,没有匹配到返 ...
- python中logging模块
1. 日志的等级 DEBUG.INFO.NOTICE.WARNING.ERROR.CRITICAL.ALERT.EMERGENCY 级别 何时使用 DEBUG 详细信息,典型地调试问题时会感兴趣. 详 ...
- C# 乐观锁、悲观锁、共享锁、排它锁、互斥锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据 ...
- 9. maps
C++有vertor,java有HashMap,C语言想使用则需要自行封装,不同的类型还需要再封装,特别麻烦. 看看Go语言的map的使用方法:var member map[string]int,创建 ...