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 ...
随机推荐
- xcode9.4 报错 error:The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.
原因 http | https 协议 不能正常使用 找到的解决方案 但是在字段名上有了变化,不过复制进去 还是会自动选择对应的 解决办法 1. 在Info.plist中添加 App Transpor ...
- 39_redux_counter应用_redux版本
一.下载 要想使用redux,首先要下载它 npm install --save redux 二.核心API 1.createStore() 作用:创建包含指定reducer的store对象 编码:i ...
- nginx warn an upstream response is buffered to a temporary file /var/cache/nginx/proxy_temp/ while reading upstream
最近管理的nginx发现大量的error log,log内容如下: an upstream response is buffered to a temporary file /var/cache/ng ...
- win10 开发mfc 64位 ocx控件
问题1.模块“XXX.ocx”加载失败 解决办法:项目--〉属性--〉常规-〉配置类型-〉 动态库(.dll) 修改为 静态库(.lib) 问题2.1>x64\Release\stdafx.o ...
- EF 数据迁移 常见错误
1.错误 “LC.exe”已退出,代码为 -1 原因:解决方案出错,而非迁移的项目
- SQL函数总结
字符串函数
- OGRE中Any 类型的实现
[OGRE中Any类型的实现] OGRE中实现了一个class Any,使用Any 可以在上下文中传递任意类型的数据.其本质实现原理就是通过指针. Any 只包含一个成员变量,类型为 placehol ...
- 配置错误 不能在此路径中使用此配置节。如果在父级别上锁定了该节,便会出现这种情况。锁定是默认设置的(overrideModeDefault="Deny"),或者是通过包含 overrideMode="Deny" 或旧有的 allowOverride="false" 的位置标记明确设置的。
原因:可能是在安装IIS7的时候没有安装asp.net, 尝试使用以下方法: cmd.exe要以管理员身份启动,在c:\windows\system32下找到cmd.exe,右键管理员启动,输入命令 ...
- windows搭建zabbix agent
1.下载和解压zabbix agent 地址: http://www.zabbix.com/downloads/2.4.4/zabbix_agents_2.4.4.win.zip解压:conf目录存放 ...
- 编写高效的 CSS 选择器
高效的CSS已经不是一个新的话题了,也不是我一个非得重拾的话题,但它却是我在Sky公司工作之时,所感兴趣的,关注已久的话题. 有很多人都忘记了,或在简单的说没有意识到,CSS在我们手中,既能很高效,也 ...